Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a422aa4ee | |||
| f014595bc5 | |||
| 6186a0d8bd | |||
| b28c19cb46 | |||
| 9b2b87d6ba | |||
| dbce287834 | |||
| 78c44d8de6 | |||
| d0916e7b39 | |||
| a55df0be90 | |||
| fc0d1fa1cc | |||
| 5facca2566 | |||
| 141e310320 | |||
| 76be3bcd09 | |||
| 84a6f118dc | |||
| 3176e6e414 | |||
| 83c632f2dc | |||
| 15a93466b6 | |||
| 3942569fb3 | |||
| ccf60a31e7 | |||
| 000fdb8e79 | |||
| 8b213ec619 | |||
| b29aaaa1a3 | |||
| 7cb1097899 | |||
| 088bc58102 | |||
| 229f99ea19 | |||
| 51a92ea45e | |||
| 1a4811cb02 | |||
| fc2508af22 | |||
| ba321fdc23 | |||
| d5bfb66eee |
@@ -10,7 +10,7 @@ Contao 5 Bundle für Organisations-, Veranstaltungsort- und Event-Verwaltung ink
|
|||||||
## Installation (lokal via Composer)
|
## Installation (lokal via Composer)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
composer require mummert-media/eventmanager-bundle
|
composer require mummert/eventmanager-bundle
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation im Contao Manager (VCS)
|
## Installation im Contao Manager (VCS)
|
||||||
@@ -18,7 +18,7 @@ composer require mummert-media/eventmanager-bundle
|
|||||||
1. **Contao Manager** → **Composer** → **Repositories** → **Repository hinzufügen**
|
1. **Contao Manager** → **Composer** → **Repositories** → **Repository hinzufügen**
|
||||||
2. Typ: `vcs`
|
2. Typ: `vcs`
|
||||||
3. URL: `https://github.com/<owner>/eventmanager-bundle`
|
3. URL: `https://github.com/<owner>/eventmanager-bundle`
|
||||||
4. Paket hinzufügen: `mummert-media/eventmanager-bundle`
|
4. Paket hinzufügen: `mummert/eventmanager-bundle`
|
||||||
5. Bei Bedarf Version `dev-main` oder ein Tag (z. B. `1.0.0`) wählen
|
5. Bei Bedarf Version `dev-main` oder ein Tag (z. B. `1.0.0`) wählen
|
||||||
6. Änderungen anwenden
|
6. Änderungen anwenden
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "mummert-media/eventmanager-bundle",
|
"name": "mummert/eventmanager-bundle",
|
||||||
"description": "Internal Contao backend bundle for organizations, locations and event/member assignments.",
|
"description": "Internal Contao backend bundle for organizations, locations and event/member assignments.",
|
||||||
"type": "contao-bundle",
|
"type": "contao-bundle",
|
||||||
"license": "proprietary",
|
"license": "proprietary",
|
||||||
@@ -11,16 +11,17 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^8.3",
|
"php": "^8.3",
|
||||||
"contao/core-bundle": "^5.7",
|
"contao/core-bundle": "^5.7",
|
||||||
|
"contao/calendar-bundle": "^5.7",
|
||||||
"contao/manager-plugin": "^2.0",
|
"contao/manager-plugin": "^2.0",
|
||||||
"numero2/contao-tags": "^0.5"
|
"numero2/contao-tags": "^0.5"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"MummertMedia\\EventManagerBundle\\": "src/"
|
"Mummert\\EventManagerBundle\\": "src/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"contao-manager-plugin": "MummertMedia\\EventManagerBundle\\Contao\\Manager\\Plugin"
|
"contao-manager-plugin": "Mummert\\EventManagerBundle\\Contao\\Manager\\Plugin"
|
||||||
},
|
},
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"config": {
|
"config": {
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ services:
|
|||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
MummertMedia\EventManagerBundle\:
|
Mummert\EventManagerBundle\:
|
||||||
resource: ../src/
|
resource: ../src/
|
||||||
exclude:
|
exclude:
|
||||||
- ../src/DependencyInjection/
|
- ../src/DependencyInjection/
|
||||||
- ../src/Contao/Manager/
|
- ../src/Contao/Manager/
|
||||||
- ../src/MummertMediaEventManagerBundle.php
|
- ../src/EventManagerBundle.php
|
||||||
|
|
||||||
MummertMedia\EventManagerBundle\EventListener\DataContainer\SetDateAddedCallback:
|
Mummert\EventManagerBundle\EventListener\DataContainer\SetDateAddedCallback:
|
||||||
tags:
|
tags:
|
||||||
- { name: contao.callback, table: tl_organization, target: config.onbeforesubmit, method: onBeforeSubmit }
|
- { name: contao.callback, table: tl_organization, target: config.onbeforesubmit, method: onBeforeSubmit }
|
||||||
- { name: contao.callback, table: tl_location, target: config.onbeforesubmit, method: onBeforeSubmit }
|
- { name: contao.callback, table: tl_location, target: config.onbeforesubmit, method: onBeforeSubmit }
|
||||||
|
|
||||||
MummertMedia\EventManagerBundle\Service\MapModuleDataProvider: ~
|
Mummert\EventManagerBundle\Service\MapModuleDataProvider: ~
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
$GLOBALS['TL_MODELS']['tl_organization'] = MummertMedia\EventManagerBundle\Model\OrganizationModel::class;
|
$GLOBALS['TL_MODELS']['tl_organization'] = Mummert\EventManagerBundle\Model\OrganizationModel::class;
|
||||||
$GLOBALS['TL_MODELS']['tl_location'] = MummertMedia\EventManagerBundle\Model\LocationModel::class;
|
$GLOBALS['TL_MODELS']['tl_location'] = Mummert\EventManagerBundle\Model\LocationModel::class;
|
||||||
|
|
||||||
$GLOBALS['BE_MOD']['content']['eventmanager_organisationen'] = [
|
$GLOBALS['BE_MOD']['content']['eventmanager_organisationen'] = [
|
||||||
'tables' => ['tl_organization'],
|
'tables' => ['tl_organization'],
|
||||||
|
|||||||
@@ -9,10 +9,43 @@ use Contao\StringUtil;
|
|||||||
$GLOBALS['TL_DCA']['tl_module']['palettes']['member_organizations'] = '{title_legend},name,headline,type;{eventmanager_legend},editPage;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
$GLOBALS['TL_DCA']['tl_module']['palettes']['member_organizations'] = '{title_legend},name,headline,type;{eventmanager_legend},editPage;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
||||||
$GLOBALS['TL_DCA']['tl_module']['palettes']['member_events'] = '{title_legend},name,headline,type;{eventmanager_legend},editPage;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
$GLOBALS['TL_DCA']['tl_module']['palettes']['member_events'] = '{title_legend},name,headline,type;{eventmanager_legend},editPage;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
||||||
$GLOBALS['TL_DCA']['tl_module']['palettes']['event_filter'] = '{title_legend},name,headline,type;{eventmanager_legend},cal_calendar,eventListDomId;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
$GLOBALS['TL_DCA']['tl_module']['palettes']['event_filter'] = '{title_legend},name,headline,type;{eventmanager_legend},cal_calendar,eventListDomId;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
||||||
$GLOBALS['TL_DCA']['tl_module']['palettes']['eventmanager_map'] = '{title_legend},name,headline,type;{eventmanager_legend},mapShowOrganizations,organizationTypeTags,mapShowExternalOrganizations,mapShowEvents,mapInitialDisplay,mapEventColor,mapOrganizationColor,mapCenterMode;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
$GLOBALS['TL_DCA']['tl_module']['palettes']['eventmanager_map'] = '{title_legend},name,headline,type;{eventmanager_legend},mapShowOrganizations,mapOrganizationListPage,organizationTypeTags,mapShowExternalOrganizations,mapShowEvents,mapInitialDisplay,mapEventColor,mapOrganizationColor,mapPitch,mapCenterMode;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
||||||
$GLOBALS['TL_DCA']['tl_module']['palettes']['organization_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,logoFolder,organizationTypeTags;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
$GLOBALS['TL_DCA']['tl_module']['palettes']['organization_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,logoFolder,organizationTypeTags;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
||||||
$GLOBALS['TL_DCA']['tl_module']['palettes']['event_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,eventFolder,termsPage,frontendAuthorId,frontendArchiveId,eventTypeTags;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
$GLOBALS['TL_DCA']['tl_module']['palettes']['event_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,eventFolder,termsPage,frontendAuthorId,frontendArchiveId,eventTypeTags;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
|
||||||
|
|
||||||
|
$getUsedOrganizationTagOptions = static function (): array {
|
||||||
|
$database = Database::getInstance();
|
||||||
|
$labelExpression = $database->fieldExists('title', 'tl_tags')
|
||||||
|
? "COALESCE(NULLIF(t.title, ''), t.tag)"
|
||||||
|
: 't.tag';
|
||||||
|
|
||||||
|
$rows = $database
|
||||||
|
->prepare(sprintf(
|
||||||
|
'SELECT DISTINCT t.id, %1$s AS label
|
||||||
|
FROM tl_tags_rel r
|
||||||
|
INNER JOIN tl_tags t ON t.id=r.tag_id
|
||||||
|
WHERE r.ptable=? AND (r.field=? OR r.field IS NULL OR r.field = \'\')
|
||||||
|
ORDER BY label ASC',
|
||||||
|
$labelExpression,
|
||||||
|
))
|
||||||
|
->execute('tl_organization', 'tags')
|
||||||
|
->fetchAllAssoc();
|
||||||
|
|
||||||
|
$options = [];
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$label = trim((string) ($row['label'] ?? ''));
|
||||||
|
|
||||||
|
if ('' === $label) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$options[(int) $row['id']] = $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
};
|
||||||
|
|
||||||
$GLOBALS['TL_DCA']['tl_module']['fields']['editPage'] = [
|
$GLOBALS['TL_DCA']['tl_module']['fields']['editPage'] = [
|
||||||
'label' => &$GLOBALS['TL_LANG']['tl_module']['editPage'],
|
'label' => &$GLOBALS['TL_LANG']['tl_module']['editPage'],
|
||||||
'exclude' => true,
|
'exclude' => true,
|
||||||
@@ -73,20 +106,7 @@ $GLOBALS['TL_DCA']['tl_module']['fields']['organizationTypeTags'] = [
|
|||||||
'label' => &$GLOBALS['TL_LANG']['tl_module']['organizationTypeTags'],
|
'label' => &$GLOBALS['TL_LANG']['tl_module']['organizationTypeTags'],
|
||||||
'exclude' => true,
|
'exclude' => true,
|
||||||
'inputType' => 'checkbox',
|
'inputType' => 'checkbox',
|
||||||
'options_callback' => static function () {
|
'options_callback' => $getUsedOrganizationTagOptions,
|
||||||
$rows = Database::getInstance()
|
|
||||||
->prepare('SELECT DISTINCT t.id, t.tag FROM tl_tags t LEFT JOIN tl_tags_rel r ON r.tag_id=t.id AND r.ptable=? AND r.field=? ORDER BY t.tag ASC')
|
|
||||||
->execute('tl_organization', 'tags')
|
|
||||||
->fetchAllAssoc();
|
|
||||||
|
|
||||||
$options = [];
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$options[(int) $row['id']] = (string) $row['tag'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $options;
|
|
||||||
},
|
|
||||||
'eval' => ['multiple' => true, 'tl_class' => 'clr'],
|
'eval' => ['multiple' => true, 'tl_class' => 'clr'],
|
||||||
'sql' => ['type' => 'blob', 'notnull' => false],
|
'sql' => ['type' => 'blob', 'notnull' => false],
|
||||||
'save_callback' => [
|
'save_callback' => [
|
||||||
@@ -173,6 +193,14 @@ $GLOBALS['TL_DCA']['tl_module']['fields']['mapShowOrganizations'] = [
|
|||||||
'sql' => ['type' => 'string', 'length' => 1, 'fixed' => true, 'default' => ''],
|
'sql' => ['type' => 'string', 'length' => 1, 'fixed' => true, 'default' => ''],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$GLOBALS['TL_DCA']['tl_module']['fields']['mapOrganizationListPage'] = [
|
||||||
|
'label' => &$GLOBALS['TL_LANG']['tl_module']['mapOrganizationListPage'],
|
||||||
|
'exclude' => true,
|
||||||
|
'inputType' => 'pageTree',
|
||||||
|
'eval' => ['fieldType' => 'radio', 'mandatory' => false, 'tl_class' => 'w50'],
|
||||||
|
'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0],
|
||||||
|
];
|
||||||
|
|
||||||
$GLOBALS['TL_DCA']['tl_module']['fields']['mapShowExternalOrganizations'] = [
|
$GLOBALS['TL_DCA']['tl_module']['fields']['mapShowExternalOrganizations'] = [
|
||||||
'label' => &$GLOBALS['TL_LANG']['tl_module']['mapShowExternalOrganizations'],
|
'label' => &$GLOBALS['TL_LANG']['tl_module']['mapShowExternalOrganizations'],
|
||||||
'exclude' => true,
|
'exclude' => true,
|
||||||
@@ -203,20 +231,7 @@ $GLOBALS['TL_DCA']['tl_module']['fields']['mapInitialOrganizationTagId'] = [
|
|||||||
'label' => &$GLOBALS['TL_LANG']['tl_module']['mapInitialOrganizationTagId'],
|
'label' => &$GLOBALS['TL_LANG']['tl_module']['mapInitialOrganizationTagId'],
|
||||||
'exclude' => true,
|
'exclude' => true,
|
||||||
'inputType' => 'select',
|
'inputType' => 'select',
|
||||||
'options_callback' => static function (): array {
|
'options_callback' => $getUsedOrganizationTagOptions,
|
||||||
$rows = Database::getInstance()
|
|
||||||
->prepare('SELECT DISTINCT t.id, t.tag FROM tl_tags t LEFT JOIN tl_tags_rel r ON r.tag_id=t.id AND r.ptable=? AND r.field=? ORDER BY t.tag ASC')
|
|
||||||
->execute('tl_organization', 'tags')
|
|
||||||
->fetchAllAssoc();
|
|
||||||
|
|
||||||
$options = [];
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$options[(int) $row['id']] = (string) $row['tag'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $options;
|
|
||||||
},
|
|
||||||
'eval' => ['includeBlankOption' => true, 'chosen' => true, 'tl_class' => 'w50'],
|
'eval' => ['includeBlankOption' => true, 'chosen' => true, 'tl_class' => 'w50'],
|
||||||
'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0],
|
'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0],
|
||||||
];
|
];
|
||||||
@@ -271,6 +286,14 @@ $GLOBALS['TL_DCA']['tl_module']['fields']['mapCenterZoom'] = [
|
|||||||
'sql' => ['type' => 'smallint', 'unsigned' => true, 'default' => 12],
|
'sql' => ['type' => 'smallint', 'unsigned' => true, 'default' => 12],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$GLOBALS['TL_DCA']['tl_module']['fields']['mapPitch'] = [
|
||||||
|
'label' => &$GLOBALS['TL_LANG']['tl_module']['mapPitch'],
|
||||||
|
'exclude' => true,
|
||||||
|
'inputType' => 'text',
|
||||||
|
'eval' => ['rgxp' => 'digit', 'maxlength' => 2, 'tl_class' => 'w50'],
|
||||||
|
'sql' => ['type' => 'smallint', 'unsigned' => true, 'default' => 0],
|
||||||
|
];
|
||||||
|
|
||||||
if (isset($GLOBALS['TL_DCA']['tl_module']['fields']['list_layout'])) {
|
if (isset($GLOBALS['TL_DCA']['tl_module']['fields']['list_layout'])) {
|
||||||
$GLOBALS['TL_DCA']['tl_module']['fields']['list_layout']['options_callback'] = static function (): array {
|
$GLOBALS['TL_DCA']['tl_module']['fields']['list_layout']['options_callback'] = static function (): array {
|
||||||
$options = Controller::getTemplateGroup('list_');
|
$options = Controller::getTemplateGroup('list_');
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ $GLOBALS['TL_LANG']['tl_module']['eventListDomId'] = ['Eventlisten-ID (DOM)', 'W
|
|||||||
$GLOBALS['TL_LANG']['tl_module']['organizationTypeTags'] = ['Anzuzeigende Organisationstypen', 'Optional: Begrenzen Sie die im Frontend anzeigbaren Organisationstyp-Tags. Leer = alle.'];
|
$GLOBALS['TL_LANG']['tl_module']['organizationTypeTags'] = ['Anzuzeigende Organisationstypen', 'Optional: Begrenzen Sie die im Frontend anzeigbaren Organisationstyp-Tags. Leer = alle.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['eventTypeTags'] = ['Anzuzeigende Veranstaltungstypen', 'Optional: Begrenzen Sie die im Frontend anzeigbaren Veranstaltungstyp-Tags. Leer = alle.'];
|
$GLOBALS['TL_LANG']['tl_module']['eventTypeTags'] = ['Anzuzeigende Veranstaltungstypen', 'Optional: Begrenzen Sie die im Frontend anzeigbaren Veranstaltungstyp-Tags. Leer = alle.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapShowOrganizations'] = ['Organisationen anzeigen', 'Wenn aktiviert, werden Organisations-Marker auf der Karte dargestellt.'];
|
$GLOBALS['TL_LANG']['tl_module']['mapShowOrganizations'] = ['Organisationen anzeigen', 'Wenn aktiviert, werden Organisations-Marker auf der Karte dargestellt.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_module']['mapOrganizationListPage'] = ['Seite für Organisationsliste', 'Optional: Seite, auf der die Organisationsliste liegt. Wird für den Link „mehr Infos“ im Organisations-Popup verwendet.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapShowExternalOrganizations'] = ['Externe Organisationen anzeigen', 'Wenn aktiviert, werden externe Organisationen (isExternal=1) zusätzlich auf der Karte dargestellt. Standard: nein.'];
|
$GLOBALS['TL_LANG']['tl_module']['mapShowExternalOrganizations'] = ['Externe Organisationen anzeigen', 'Wenn aktiviert, werden externe Organisationen (isExternal=1) zusätzlich auf der Karte dargestellt. Standard: nein.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapShowEvents'] = ['Veranstaltungen anzeigen', 'Wenn aktiviert, werden Event- (inkl. Orts-) Marker auf der Karte dargestellt.'];
|
$GLOBALS['TL_LANG']['tl_module']['mapShowEvents'] = ['Veranstaltungen anzeigen', 'Wenn aktiviert, werden Event- (inkl. Orts-) Marker auf der Karte dargestellt.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapInitialDisplay'] = ['Initiale Anzeige', 'Definiert, welche Marker beim Laden der Karte initial angezeigt werden.'];
|
$GLOBALS['TL_LANG']['tl_module']['mapInitialDisplay'] = ['Initiale Anzeige', 'Definiert, welche Marker beim Laden der Karte initial angezeigt werden.'];
|
||||||
@@ -33,3 +34,4 @@ $GLOBALS['TL_LANG']['tl_module']['mapCenterMode_options'] = [
|
|||||||
$GLOBALS['TL_LANG']['tl_module']['mapCenterLat'] = ['Breitengrad (Center)', 'Breitengrad für die feste Kartenzentrierung, z. B. 51.0538'];
|
$GLOBALS['TL_LANG']['tl_module']['mapCenterLat'] = ['Breitengrad (Center)', 'Breitengrad für die feste Kartenzentrierung, z. B. 51.0538'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapCenterLng'] = ['Längengrad (Center)', 'Längengrad für die feste Kartenzentrierung, z. B. 13.3080'];
|
$GLOBALS['TL_LANG']['tl_module']['mapCenterLng'] = ['Längengrad (Center)', 'Längengrad für die feste Kartenzentrierung, z. B. 13.3080'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapCenterZoom'] = ['Zoom-Level (Center)', 'Zoom-Level für die feste Kartenzentrierung (z. B. 12).'];
|
$GLOBALS['TL_LANG']['tl_module']['mapCenterZoom'] = ['Zoom-Level (Center)', 'Zoom-Level für die feste Kartenzentrierung (z. B. 12).'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_module']['mapPitch'] = ['Pitch (Neigung)', 'Kartenneigung in Grad (0-85). Standard bei leerer Angabe: 0.'];
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ $GLOBALS['TL_LANG']['tl_module']['eventListDomId'] = ['Event list ID (DOM)', 'Se
|
|||||||
$GLOBALS['TL_LANG']['tl_module']['organizationTypeTags'] = ['Displayed organization types', 'Optional: Limit which organization type tags can be selected in the frontend. Empty = all.'];
|
$GLOBALS['TL_LANG']['tl_module']['organizationTypeTags'] = ['Displayed organization types', 'Optional: Limit which organization type tags can be selected in the frontend. Empty = all.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['eventTypeTags'] = ['Displayed event types', 'Optional: Limit which event type tags can be selected in the frontend. Empty = all.'];
|
$GLOBALS['TL_LANG']['tl_module']['eventTypeTags'] = ['Displayed event types', 'Optional: Limit which event type tags can be selected in the frontend. Empty = all.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapShowOrganizations'] = ['Show organizations', 'If enabled, organization markers are rendered on the map.'];
|
$GLOBALS['TL_LANG']['tl_module']['mapShowOrganizations'] = ['Show organizations', 'If enabled, organization markers are rendered on the map.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_module']['mapOrganizationListPage'] = ['Organization list page', 'Optional: page containing the organization list. Used for the "more info" link in organization popups.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapShowExternalOrganizations'] = ['Show external organizations', 'If enabled, external organizations (isExternal=1) are additionally rendered on the map. Default: no.'];
|
$GLOBALS['TL_LANG']['tl_module']['mapShowExternalOrganizations'] = ['Show external organizations', 'If enabled, external organizations (isExternal=1) are additionally rendered on the map. Default: no.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapShowEvents'] = ['Show events', 'If enabled, event markers (including related locations) are rendered on the map.'];
|
$GLOBALS['TL_LANG']['tl_module']['mapShowEvents'] = ['Show events', 'If enabled, event markers (including related locations) are rendered on the map.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapInitialDisplay'] = ['Initial display', 'Defines which markers are initially shown when the map is loaded.'];
|
$GLOBALS['TL_LANG']['tl_module']['mapInitialDisplay'] = ['Initial display', 'Defines which markers are initially shown when the map is loaded.'];
|
||||||
@@ -33,3 +34,4 @@ $GLOBALS['TL_LANG']['tl_module']['mapCenterMode_options'] = [
|
|||||||
$GLOBALS['TL_LANG']['tl_module']['mapCenterLat'] = ['Center latitude', 'Latitude for fixed map centering, e.g. 51.0538'];
|
$GLOBALS['TL_LANG']['tl_module']['mapCenterLat'] = ['Center latitude', 'Latitude for fixed map centering, e.g. 51.0538'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapCenterLng'] = ['Center longitude', 'Longitude for fixed map centering, e.g. 13.3080'];
|
$GLOBALS['TL_LANG']['tl_module']['mapCenterLng'] = ['Center longitude', 'Longitude for fixed map centering, e.g. 13.3080'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['mapCenterZoom'] = ['Center zoom level', 'Zoom level for fixed map centering (e.g. 12).'];
|
$GLOBALS['TL_LANG']['tl_module']['mapCenterZoom'] = ['Center zoom level', 'Zoom level for fixed map centering (e.g. 12).'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_module']['mapPitch'] = ['Pitch', 'Map pitch in degrees (0-85). Default when empty: 0.'];
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
<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>
|
||||||
|
<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' } }) }}
|
{{ 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 }}">
|
||||||
@@ -23,8 +27,14 @@
|
|||||||
{{ form_row(form.title) }}
|
{{ form_row(form.title) }}
|
||||||
{{ form_row(form.startDate) }}
|
{{ form_row(form.startDate) }}
|
||||||
{{ form_row(form.endDate) }}
|
{{ form_row(form.endDate) }}
|
||||||
{{ form_row(form.addTime) }}
|
<div class="widget fullwidth">
|
||||||
<div id="event-time-wrap" style="display:none;" hidden aria-hidden="true">
|
<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.startTime) }}
|
||||||
{{ form_row(form.endTime) }}
|
{{ form_row(form.endTime) }}
|
||||||
</div>
|
</div>
|
||||||
@@ -32,22 +42,162 @@
|
|||||||
{{ form_row(form.organization_ids) }}
|
{{ form_row(form.organization_ids) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ form_row(form.location_id) }}
|
{{ form_row(form.location_id) }}
|
||||||
{{ form_row(form.tags) }}
|
|
||||||
{{ form_row(form.teaser) }}
|
|
||||||
{{ form_row(form.description) }}
|
|
||||||
{{ form_row(form.url) }}
|
|
||||||
{{ form_row(form.addImage) }}
|
|
||||||
|
|
||||||
<div id="event-image-upload-wrap" style="display:none;" hidden aria-hidden="true">
|
<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.eventUpload) }}
|
||||||
{{ form_row(form.photographer) }}
|
{{ form_row(form.photographer) }}
|
||||||
<p class="help-text">Die Angabe des Urhebers ist notwendig. Ihnen muss eine Genehmigung zur Verwendung des Bildes vorliegen.</p>
|
<p class="help-text">Die Angabe des Urhebers ist notwendig. Ihnen muss eine Genehmigung zur Verwendung des Bildes vorliegen.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ form_row(form.termsAccepted) }}
|
<div class="widget fullwidth">
|
||||||
{{ form_row(form.isSoldOut) }}
|
<div class="ns-tag-switch-item">
|
||||||
{{ form_row(form.isCanceled) }}
|
{{ form_widget(form.termsAccepted, { attr: { class: (form.termsAccepted.vars.attr.class|default('') ~ ' ns-tag-switch-input')|trim, role: 'switch' } }) }}
|
||||||
{{ form_row(form.published) }}
|
{{ 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">
|
<div class="actions" aria-label="Formularaktionen">
|
||||||
<button type="submit" id="save-btn" disabled>Speichern</button>
|
<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>
|
<button type="submit" id="save-back-btn" name="save_back" value="1" disabled>Speichern und Zurück</button>
|
||||||
@@ -59,7 +209,7 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const locationSelect = document.querySelector('select.js-location-choice');
|
const locationSelect = document.querySelector('select.js-location-choice');
|
||||||
const organizationSelect = document.querySelector('select.js-organization-choice');
|
const organizationSelect = document.querySelector('select.js-organization-choice');
|
||||||
const typeSelect = document.querySelector('select.js-event-tags-choice');
|
const tagSwitches = document.querySelectorAll('input.ns-tag-switch-input[role="switch"]');
|
||||||
|
|
||||||
if (organizationSelect && typeof window.Choices === 'function') {
|
if (organizationSelect && typeof window.Choices === 'function') {
|
||||||
new window.Choices(organizationSelect, {
|
new window.Choices(organizationSelect, {
|
||||||
@@ -86,18 +236,21 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeSelect && typeof window.Choices === 'function') {
|
const syncSwitchState = (input) => {
|
||||||
new window.Choices(typeSelect, {
|
const isChecked = !!input.checked;
|
||||||
searchEnabled: false,
|
input.setAttribute('aria-checked', isChecked ? 'true' : 'false');
|
||||||
shouldSort: false,
|
|
||||||
removeItemButton: true,
|
const stateNode = document.querySelector(`[data-switch-state-for="${input.id}"]`);
|
||||||
itemSelectText: '',
|
|
||||||
placeholder: true,
|
if (stateNode) {
|
||||||
placeholderValue: typeSelect.dataset.placeholder || 'Typen suchen …',
|
stateNode.textContent = isChecked ? 'Ein' : 'Aus';
|
||||||
noResultsText: 'Keine Treffer',
|
}
|
||||||
noChoicesText: 'Keine Optionen verfügbar'
|
};
|
||||||
});
|
|
||||||
}
|
tagSwitches.forEach((input) => {
|
||||||
|
syncSwitchState(input);
|
||||||
|
input.addEventListener('change', () => syncSwitchState(input));
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof window.flatpickr === 'function') {
|
if (typeof window.flatpickr === 'function') {
|
||||||
const dateInputs = document.querySelectorAll('input.js-flatpickr-date');
|
const dateInputs = document.querySelectorAll('input.js-flatpickr-date');
|
||||||
@@ -212,14 +365,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>'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<option value="tag-{{ tag.id }}">{{ tag.title }} ({{ tag.count }})</option>
|
<option value="tag-{{ tag.id }}">{{ tag.title }} ({{ tag.count }})</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
<button type="button" class="eventfilter-clear" data-eventfilter-clear="tag" aria-label="Kategorie-Filter zurücksetzen" hidden><span class="icon-cross"></span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="widget-select places">
|
<div class="widget-select places">
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
<option value="location-{{ location.id }}">{{ location.title }} ({{ location.count }})</option>
|
<option value="location-{{ location.id }}">{{ location.title }} ({{ location.count }})</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
<button type="button" class="eventfilter-clear" data-eventfilter-clear="location" aria-label="Ort-Filter zurücksetzen" hidden><span class="icon-cross"></span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="widget-select org">
|
<div class="widget-select org">
|
||||||
@@ -28,60 +30,26 @@
|
|||||||
<option value="org-{{ organization.id }}">{{ organization.title }} ({{ organization.count }})</option>
|
<option value="org-{{ organization.id }}">{{ organization.title }} ({{ organization.count }})</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
<button type="button" class="eventfilter-clear" data-eventfilter-clear="org" aria-label="Veranstalter-Filter zurücksetzen" hidden><span class="icon-cross"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" id="eventfilter-reset" class="eventfilter-reset" aria-label="Alle Filter zurücksetzen" aria-controls="{{ targetEventListId|default('eventlist')|e('html_attr') }}" hidden>Filter zurücksetzen</button>
|
|
||||||
|
|
||||||
<p id="eventfilter-status" class="visually-hidden" aria-live="polite"></p>
|
<p id="eventfilter-status" class="visually-hidden" aria-live="polite"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.visually-hidden {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.event-filter-target-list {
|
.event-filter-target-list {
|
||||||
display: grid;
|
view-transition-name: event-filter-list;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-filter-target-list .event {
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
opacity: 1;
|
::view-transition-group(event-filter-list),
|
||||||
transform: translateY(0);
|
::view-transition-old(event-filter-list),
|
||||||
transition: opacity 220ms ease, transform 220ms ease;
|
::view-transition-new(event-filter-list) {
|
||||||
will-change: opacity, transform;
|
animation-duration: 240ms;
|
||||||
|
animation-timing-function: ease;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-filter-target-list .event.is-filtered-out {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(6px);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#eventfilters .widget-select.active select {
|
|
||||||
outline: 3px solid currentColor;
|
|
||||||
outline-offset: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#eventfilters select:focus-visible {
|
|
||||||
outline: 3px solid currentColor;
|
|
||||||
outline-offset: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#eventfilters .eventfilter-reset[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
@@ -93,12 +61,59 @@
|
|||||||
|
|
||||||
const targetEventListId = filters.dataset.eventlistId || 'eventlist';
|
const targetEventListId = filters.dataset.eventlistId || 'eventlist';
|
||||||
const list = document.getElementById(targetEventListId);
|
const list = document.getElementById(targetEventListId);
|
||||||
|
const initialStoredEventListView = (() => {
|
||||||
|
try {
|
||||||
|
return (window.localStorage.getItem('eventlist_view') || '').trim();
|
||||||
|
} catch (error) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const applyDefaultGrid = (targetList) => {
|
||||||
|
if (!targetList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetList.classList.add('event-filter-target-list');
|
||||||
|
targetList.classList.add('grid');
|
||||||
|
};
|
||||||
|
|
||||||
if (!list) {
|
if (!list) {
|
||||||
console.warn(`[event_filter] Target event list #${targetEventListId} was not found.`);
|
console.warn(`[event_filter] Target event list #${targetEventListId} was not found.`);
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
applyDefaultGrid(document.getElementById(targetEventListId));
|
||||||
|
}, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
list?.classList.add('event-filter-target-list');
|
applyDefaultGrid(list);
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if ('' !== initialStoredEventListView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetList = document.getElementById(targetEventListId);
|
||||||
|
|
||||||
|
if (!targetList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetList.classList.remove('list');
|
||||||
|
applyDefaultGrid(targetList);
|
||||||
|
|
||||||
|
const toggleToList = document.getElementById('toggletolist');
|
||||||
|
const toggleToGrid = document.getElementById('toggletogrid');
|
||||||
|
|
||||||
|
toggleToList?.classList.remove('disabled');
|
||||||
|
toggleToGrid?.classList.add('disabled');
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem('eventlist_view', 'grid');
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
const events = list ? Array.from(list.querySelectorAll(':scope > .event')) : [];
|
const events = list ? Array.from(list.querySelectorAll(':scope > .event')) : [];
|
||||||
const tagSelect = filters.querySelector('#tag-filter');
|
const tagSelect = filters.querySelector('#tag-filter');
|
||||||
@@ -107,16 +122,41 @@
|
|||||||
const tagWidget = tagSelect?.closest('.widget-select');
|
const tagWidget = tagSelect?.closest('.widget-select');
|
||||||
const locationWidget = locationSelect?.closest('.widget-select');
|
const locationWidget = locationSelect?.closest('.widget-select');
|
||||||
const orgWidget = orgSelect?.closest('.widget-select');
|
const orgWidget = orgSelect?.closest('.widget-select');
|
||||||
const resetButton = filters.querySelector('#eventfilter-reset');
|
const clearTagButton = filters.querySelector('[data-eventfilter-clear="tag"]');
|
||||||
|
const clearLocationButton = filters.querySelector('[data-eventfilter-clear="location"]');
|
||||||
|
const clearOrgButton = filters.querySelector('[data-eventfilter-clear="org"]');
|
||||||
const status = filters.querySelector('#eventfilter-status');
|
const status = filters.querySelector('#eventfilter-status');
|
||||||
const stateStorageKey = 'event-filter-state';
|
const stateStorageKey = 'event-filter-state';
|
||||||
const stateQueryKey = 'event_filter';
|
const stateQueryKey = 'event_filter';
|
||||||
|
|
||||||
const animationMs = 220;
|
const animationMs = 220;
|
||||||
let hideTimers = new WeakMap();
|
let hideTimers = new WeakMap();
|
||||||
|
const supportsViewTransitions = typeof document.startViewTransition === 'function';
|
||||||
|
let isViewTransitionMutation = false;
|
||||||
let currentFilter = { type: 'all', value: '' };
|
let currentFilter = { type: 'all', value: '' };
|
||||||
let suppressedChangeEvents = 0;
|
let suppressedChangeEvents = 0;
|
||||||
|
|
||||||
|
const runWithLayoutTransition = (mutation) => {
|
||||||
|
if (!supportsViewTransitions) {
|
||||||
|
mutation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.startViewTransition(() => {
|
||||||
|
isViewTransitionMutation = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mutation();
|
||||||
|
} finally {
|
||||||
|
isViewTransitionMutation = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
mutation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const hasOptionValue = (selectElement, value) => {
|
const hasOptionValue = (selectElement, value) => {
|
||||||
if (!selectElement) {
|
if (!selectElement) {
|
||||||
return false;
|
return false;
|
||||||
@@ -251,7 +291,16 @@
|
|||||||
|
|
||||||
const showEvent = (eventItem) => {
|
const showEvent = (eventItem) => {
|
||||||
clearHideTimer(eventItem);
|
clearHideTimer(eventItem);
|
||||||
eventItem.hidden = false;
|
|
||||||
|
if (eventItem.hidden) {
|
||||||
|
if (supportsViewTransitions && !isViewTransitionMutation) {
|
||||||
|
runWithLayoutTransition(() => {
|
||||||
|
eventItem.hidden = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
eventItem.hidden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
eventItem.classList.remove('is-filtered-out');
|
eventItem.classList.remove('is-filtered-out');
|
||||||
@@ -263,7 +312,14 @@
|
|||||||
eventItem.classList.add('is-filtered-out');
|
eventItem.classList.add('is-filtered-out');
|
||||||
|
|
||||||
const timer = window.setTimeout(() => {
|
const timer = window.setTimeout(() => {
|
||||||
eventItem.hidden = true;
|
if (supportsViewTransitions && !isViewTransitionMutation) {
|
||||||
|
runWithLayoutTransition(() => {
|
||||||
|
eventItem.hidden = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
eventItem.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
hideTimers.delete(eventItem);
|
hideTimers.delete(eventItem);
|
||||||
}, animationMs);
|
}, animationMs);
|
||||||
|
|
||||||
@@ -279,9 +335,25 @@
|
|||||||
locationWidget?.classList.toggle('active', hasActiveLocation);
|
locationWidget?.classList.toggle('active', hasActiveLocation);
|
||||||
orgWidget?.classList.toggle('active', hasActiveOrg);
|
orgWidget?.classList.toggle('active', hasActiveOrg);
|
||||||
|
|
||||||
if (resetButton) {
|
if (clearTagButton) {
|
||||||
resetButton.hidden = !(hasActiveTag || hasActiveLocation || hasActiveOrg);
|
clearTagButton.hidden = !hasActiveTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clearLocationButton) {
|
||||||
|
clearLocationButton.hidden = !hasActiveLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearOrgButton) {
|
||||||
|
clearOrgButton.hidden = !hasActiveOrg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetAllFilters = () => {
|
||||||
|
setSelectValue(tagSelect, '');
|
||||||
|
setSelectValue(locationSelect, '');
|
||||||
|
setSelectValue(orgSelect, '');
|
||||||
|
|
||||||
|
applyFilter({ type: 'all', value: '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseIdList = (rawValue) => (rawValue ?? '')
|
const parseIdList = (rawValue) => (rawValue ?? '')
|
||||||
@@ -314,7 +386,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibleCount = events.filter((eventItem) => !eventItem.hidden).length;
|
const visibleCount = events.filter((eventItem) => matches(eventItem, filterState)).length;
|
||||||
let filterText = 'alle';
|
let filterText = 'alle';
|
||||||
|
|
||||||
if (filterState.type === 'tag' && tagSelect) {
|
if (filterState.type === 'tag' && tagSelect) {
|
||||||
@@ -339,12 +411,14 @@
|
|||||||
currentFilter = filterState;
|
currentFilter = filterState;
|
||||||
setActiveControl(filterState);
|
setActiveControl(filterState);
|
||||||
|
|
||||||
events.forEach((eventItem) => {
|
runWithLayoutTransition(() => {
|
||||||
if (matches(eventItem, filterState)) {
|
events.forEach((eventItem) => {
|
||||||
showEvent(eventItem);
|
if (matches(eventItem, filterState)) {
|
||||||
} else {
|
showEvent(eventItem);
|
||||||
hideEvent(eventItem);
|
} else {
|
||||||
}
|
hideEvent(eventItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
updateStatus(filterState);
|
updateStatus(filterState);
|
||||||
@@ -402,13 +476,9 @@
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
resetButton?.addEventListener('click', () => {
|
clearTagButton?.addEventListener('click', resetAllFilters);
|
||||||
setSelectValue(tagSelect, '');
|
clearLocationButton?.addEventListener('click', resetAllFilters);
|
||||||
setSelectValue(locationSelect, '');
|
clearOrgButton?.addEventListener('click', resetAllFilters);
|
||||||
setSelectValue(orgSelect, '');
|
|
||||||
|
|
||||||
applyFilter({ type: 'all', value: '' });
|
|
||||||
});
|
|
||||||
|
|
||||||
const urlState = readUrlState();
|
const urlState = readUrlState();
|
||||||
const storedState = readStoredState();
|
const storedState = readStoredState();
|
||||||
|
|||||||
@@ -81,30 +81,8 @@
|
|||||||
data-map-center-lat="{{ mapCenterLat|default('')|e('html_attr') }}"
|
data-map-center-lat="{{ mapCenterLat|default('')|e('html_attr') }}"
|
||||||
data-map-center-lng="{{ mapCenterLng|default('')|e('html_attr') }}"
|
data-map-center-lng="{{ mapCenterLng|default('')|e('html_attr') }}"
|
||||||
data-map-center-zoom="{{ mapCenterZoom|default(12)|e('html_attr') }}"
|
data-map-center-zoom="{{ mapCenterZoom|default(12)|e('html_attr') }}"
|
||||||
|
data-map-pitch="{{ mapPitch|default(0)|e('html_attr') }}"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.eventmanager-map-filter {
|
|
||||||
display: grid;
|
|
||||||
gap: .5rem;
|
|
||||||
margin-bottom: .75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eventmanager-map-filter__group {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eventmanager-map-filter__tag[aria-pressed="true"] {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eventmanager-map {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 420px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script type="application/json" id="{{ mapDataElementId|e('html_attr') }}">{{ mapItemsJson|raw }}</script>
|
<script type="application/json" id="{{ mapDataElementId|e('html_attr') }}">{{ mapItemsJson|raw }}</script>
|
||||||
<script type="module" src="/bundles/mummertmediaeventmanager/assets/map-module.js?v=20260226d"></script>
|
<script type="module" src="/bundles/eventmanager/assets/map-module.js?v=20260227b"></script>
|
||||||
|
|||||||
@@ -16,30 +16,32 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if isEditor %}
|
{% if isEditor %}
|
||||||
<form method="post" style="display:inline;" aria-label="Sichtbarkeit für {{ item.title }} ändern">
|
<div class="member-events-actions">
|
||||||
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
|
<form method="post" class="member-events-action-form" aria-label="Sichtbarkeit für {{ item.title }} ändern">
|
||||||
<input type="hidden" name="action" value="toggle_published">
|
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
|
||||||
<input type="hidden" name="event_id" value="{{ item.id }}">
|
<input type="hidden" name="action" value="toggle_published">
|
||||||
<button type="submit" aria-label="{{ item.published ? 'Auf inaktiv setzen' : 'Auf aktiv setzen' }} für {{ item.title }}">{{ item.published ? 'aktiv' : 'inaktiv' }}</button>
|
<input type="hidden" name="event_id" value="{{ item.id }}">
|
||||||
</form>
|
<button type="submit" aria-label="{{ item.published ? 'Auf inaktiv setzen' : 'Auf aktiv setzen' }} für {{ item.title }}">{{ item.published ? 'aktiv' : 'inaktiv' }}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
{% if item.editUrl %}
|
{% if item.editUrl %}
|
||||||
<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" class="member-events-action-form" 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" class="member-events-action-form" 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 }}">
|
||||||
<button type="submit" aria-label="{{ item.title }} löschen">Löschen</button>
|
<button type="submit" aria-label="{{ item.title }} löschen">Löschen</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -54,6 +56,8 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<script src="/bundles/eventmanager/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>
|
||||||
@@ -69,30 +73,32 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if isEditor %}
|
{% if isEditor %}
|
||||||
<form method="post" style="display:inline;" aria-label="Sichtbarkeit für {{ item.title }} ändern">
|
<div class="member-events-actions">
|
||||||
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
|
<form method="post" class="member-events-action-form" aria-label="Sichtbarkeit für {{ item.title }} ändern">
|
||||||
<input type="hidden" name="action" value="toggle_published">
|
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
|
||||||
<input type="hidden" name="event_id" value="{{ item.id }}">
|
<input type="hidden" name="action" value="toggle_published">
|
||||||
<button type="submit" aria-label="{{ item.published ? 'Auf inaktiv setzen' : 'Auf aktiv setzen' }} für {{ item.title }}">{{ item.published ? 'aktiv' : 'inaktiv' }}</button>
|
<input type="hidden" name="event_id" value="{{ item.id }}">
|
||||||
</form>
|
<button type="submit" aria-label="{{ item.published ? 'Auf inaktiv setzen' : 'Auf aktiv setzen' }} für {{ item.title }}">{{ item.published ? 'aktiv' : 'inaktiv' }}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
{% if item.editUrl %}
|
{% if item.editUrl %}
|
||||||
<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" class="member-events-action-form" 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" class="member-events-action-form" 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 }}">
|
||||||
<button type="submit" aria-label="{{ item.title }} löschen">Löschen</button>
|
<button type="submit" aria-label="{{ item.title }} löschen">Löschen</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -5,17 +5,141 @@
|
|||||||
<p role="alert">{{ error }}</p>
|
<p role="alert">{{ error }}</p>
|
||||||
<p><a href="{{ backUrl }}">Zurück</a></p>
|
<p><a href="{{ backUrl }}">Zurück</a></p>
|
||||||
{% elseif form is defined and form %}
|
{% 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://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">
|
||||||
<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>
|
||||||
|
<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, { 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 }}">
|
||||||
<input type="hidden" id="remove_logo" name="remove_logo" value="0">
|
<input type="hidden" id="remove_logo" name="remove_logo" value="0">
|
||||||
{{ form_widget(form) }}
|
{{ form_row(form.title) }}
|
||||||
|
{{ form_row(form.street) }}
|
||||||
|
{{ form_row(form.street2) }}
|
||||||
|
{{ form_row(form.postal) }}
|
||||||
|
{{ form_row(form.city) }}
|
||||||
|
{{ form_row(form.phone) }}
|
||||||
|
{{ form_row(form.email) }}
|
||||||
|
{{ form_row(form.website) }}
|
||||||
|
|
||||||
|
<div class="widget">
|
||||||
|
{{ form_label(form.description, null, { label_attr: { id: form.description.vars.id ~ '-label' } }) }}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="event-editor-toolbar"
|
||||||
|
data-mm-editor-toolbar="{{ form.description.vars.id }}"
|
||||||
|
role="toolbar"
|
||||||
|
aria-label="Formatierungswerkzeuge Beschreibung"
|
||||||
|
aria-orientation="horizontal"
|
||||||
|
aria-describedby="{{ form.description.vars.id }}-shortcuts"
|
||||||
|
aria-controls="{{ form.description.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.description.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.description.vars.id }}-editor"
|
||||||
|
class="event-editor"
|
||||||
|
data-mm-editor="tiptap"
|
||||||
|
data-textarea-id="{{ form.description.vars.id }}"
|
||||||
|
data-editor-label="Beschreibung"
|
||||||
|
role="textbox"
|
||||||
|
aria-multiline="true"
|
||||||
|
aria-labelledby="{{ form.description.vars.id }}-label"
|
||||||
|
aria-describedby="{{ form.description.vars.id }}-shortcuts {{ form.description.vars.id }}-counter {{ form.description.vars.id }}-errors"
|
||||||
|
></div>
|
||||||
|
<div id="{{ form.description.vars.id }}-counter" class="event-editor-counter" data-mm-editor-counter-for="{{ form.description.vars.id }}" role="status" aria-live="polite"></div>
|
||||||
|
{{ form_widget(form.description, { attr: { class: 'js-organization-description-source', rows: 8, 'aria-hidden': 'true', tabindex: '-1' } }) }}
|
||||||
|
<div id="{{ form.description.vars.id }}-errors">{{ form_errors(form.description) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset class="widget" aria-describedby="organization-tags-help organization-tags-errors">
|
||||||
|
<legend>{{ form.tags.vars.label }}</legend>
|
||||||
|
<p id="organization-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="organization-tags-errors">{{ form_errors(form.tags) }}</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
{{ form_row(form.logoUpload) }}
|
||||||
|
|
||||||
<div class="actions" aria-label="Formularaktionen">
|
<div class="actions" aria-label="Formularaktionen">
|
||||||
<button type="submit" id="save-btn" disabled>Speichern</button>
|
<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>
|
<button type="submit" id="save-back-btn" name="save_back" value="1" disabled>Speichern und Zurück</button>
|
||||||
@@ -25,20 +149,23 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
const typeSelect = document.querySelector('select.js-organization-tags-choice');
|
const tagSwitches = document.querySelectorAll('input.ns-tag-switch-input[role="switch"]');
|
||||||
|
|
||||||
if (typeSelect && typeof window.Choices === 'function') {
|
const syncSwitchState = (input) => {
|
||||||
new window.Choices(typeSelect, {
|
const isChecked = !!input.checked;
|
||||||
searchEnabled: false,
|
input.setAttribute('aria-checked', isChecked ? 'true' : 'false');
|
||||||
shouldSort: false,
|
|
||||||
removeItemButton: true,
|
const stateNode = document.querySelector(`[data-switch-state-for="${input.id}"]`);
|
||||||
itemSelectText: '',
|
|
||||||
placeholder: true,
|
if (stateNode) {
|
||||||
placeholderValue: typeSelect.dataset.placeholder || 'Typ auswählen …',
|
stateNode.textContent = isChecked ? 'Ein' : 'Aus';
|
||||||
noResultsText: 'Keine Treffer',
|
}
|
||||||
noChoicesText: 'Keine Optionen verfügbar'
|
};
|
||||||
});
|
|
||||||
}
|
tagSwitches.forEach((input) => {
|
||||||
|
syncSwitchState(input);
|
||||||
|
input.addEventListener('change', () => syncSwitchState(input));
|
||||||
|
});
|
||||||
|
|
||||||
const currentLogoPath = {{ (currentLogoPath is defined and currentLogoPath) ? ('/' ~ currentLogoPath)|json_encode|raw : 'null' }};
|
const currentLogoPath = {{ (currentLogoPath is defined and currentLogoPath) ? ('/' ~ currentLogoPath)|json_encode|raw : 'null' }};
|
||||||
const logoInput = document.querySelector('input.js-logo-upload');
|
const logoInput = document.querySelector('input.js-logo-upload');
|
||||||
@@ -73,14 +200,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>'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,71 @@ const DEFAULT_ZOOM = 6;
|
|||||||
const DEFAULT_EVENT_COLOR = '#BC5067';
|
const DEFAULT_EVENT_COLOR = '#BC5067';
|
||||||
const EVENT_FADE_DURATION_MS = 200;
|
const EVENT_FADE_DURATION_MS = 200;
|
||||||
const EVENT_CLUSTER_LAYER_ID = 'eventmanager-events-clusters';
|
const EVENT_CLUSTER_LAYER_ID = 'eventmanager-events-clusters';
|
||||||
const EVENT_CLUSTER_SPIDERFY_LAYER_ID = 'eventmanager-events-clusters-spiderfy';
|
|
||||||
const EVENT_UNCLUSTERED_LAYER_ID = 'eventmanager-events-unclustered';
|
const EVENT_UNCLUSTERED_LAYER_ID = 'eventmanager-events-unclustered';
|
||||||
const SATELLITE_SOURCE_ID = 'eventmanager-satellite-source';
|
const SATELLITE_SOURCE_ID = 'eventmanager-satellite-source';
|
||||||
const SATELLITE_LAYER_ID = 'eventmanager-satellite-layer';
|
const SATELLITE_LAYER_ID = 'eventmanager-satellite-layer';
|
||||||
const SATELLITE_TILE_URL = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}';
|
const SATELLITE_TILE_URL = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}';
|
||||||
|
const SATELLITE_LABELS_SOURCE_ID = 'eventmanager-satellite-labels-source';
|
||||||
|
const SATELLITE_LABELS_LAYER_ID = 'eventmanager-satellite-labels-layer';
|
||||||
|
const SATELLITE_LABELS_TILE_URL = 'https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}';
|
||||||
|
const SATELLITE_ROADS_SOURCE_ID = 'eventmanager-satellite-roads-source';
|
||||||
|
const SATELLITE_ROADS_LAYER_ID = 'eventmanager-satellite-roads-layer';
|
||||||
|
const SATELLITE_ROADS_TILE_URL = 'https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}';
|
||||||
const SATELLITE_ATTRIBUTION = '© Esri, Maxar';
|
const SATELLITE_ATTRIBUTION = '© Esri, Maxar';
|
||||||
|
const PROTOMAPS_SOURCE_ID = 'protomaps';
|
||||||
|
const DEFAULT_BASE_STYLE_URL = 'https://maps.mummert.media/metadaten/world-light.json';
|
||||||
|
const PROTOMAPS_PM_TILES_URL = 'pmtiles://https://maps.mummert.media/tiles/world.pmtiles';
|
||||||
|
const PROTOMAPS_ATTRIBUTION = '<a href="https://github.com/protomaps/basemaps">Protomaps</a> © <a href="https://openstreetmap.org">OpenStreetMap</a>';
|
||||||
|
const PROTOMAPS_GLYPHS_URL = 'https://maps.mummert.media/fonts/{fontstack}/{range}.pbf';
|
||||||
|
const PROTOMAPS_SPRITE_URL = 'https://maps.mummert.media/sprites/v4/light';
|
||||||
|
const PROTOMAPS_FLAVOR_URL = 'https://maps.mummert.media/metadaten/flavors/bio.json';
|
||||||
|
const PROTOMAPS_BASEMAPS_ESM_URLS = [
|
||||||
|
'https://cdn.jsdelivr.net/npm/@protomaps/basemaps@5/+esm',
|
||||||
|
'https://esm.sh/@protomaps/basemaps@5',
|
||||||
|
];
|
||||||
|
const PROTOMAPS_BASEMAPS_UMD_URL = 'https://unpkg.com/@protomaps/basemaps@5/dist/basemaps.js';
|
||||||
|
|
||||||
let dependenciesPromise = null;
|
let dependenciesPromise = null;
|
||||||
|
let basemapsApiPromise = null;
|
||||||
|
const protomapsStylePromises = new Map();
|
||||||
|
|
||||||
|
const getBasemapsApi = async () => {
|
||||||
|
if (basemapsApiPromise) {
|
||||||
|
return basemapsApiPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
basemapsApiPromise = (async () => {
|
||||||
|
for (const url of PROTOMAPS_BASEMAPS_ESM_URLS) {
|
||||||
|
try {
|
||||||
|
const module = await import(url);
|
||||||
|
const moduleApi = module && 'object' === typeof module
|
||||||
|
? (module.layers ? module : (module.default && module.default.layers ? module.default : null))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (moduleApi && 'function' === typeof moduleApi.layers) {
|
||||||
|
return moduleApi;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadScript(PROTOMAPS_BASEMAPS_UMD_URL);
|
||||||
|
|
||||||
|
if (window.basemaps && 'function' === typeof window.basemaps.layers) {
|
||||||
|
return window.basemaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Failed to load Protomaps basemaps API');
|
||||||
|
})();
|
||||||
|
|
||||||
|
basemapsApiPromise = basemapsApiPromise.catch((error) => {
|
||||||
|
basemapsApiPromise = null;
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
return basemapsApiPromise;
|
||||||
|
};
|
||||||
|
|
||||||
const ensureStylesheet = (href) => {
|
const ensureStylesheet = (href) => {
|
||||||
if (!href) {
|
if (!href) {
|
||||||
@@ -89,6 +146,69 @@ const ensureDependencies = async () => {
|
|||||||
return dependenciesPromise;
|
return dependenciesPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getProtomapsStyle = async (baseStyleUrl = DEFAULT_BASE_STYLE_URL) => {
|
||||||
|
const normalizedBaseStyleUrl = String(baseStyleUrl || DEFAULT_BASE_STYLE_URL).trim() || DEFAULT_BASE_STYLE_URL;
|
||||||
|
|
||||||
|
if (protomapsStylePromises.has(normalizedBaseStyleUrl)) {
|
||||||
|
return protomapsStylePromises.get(normalizedBaseStyleUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stylePromise = (async () => {
|
||||||
|
const baseStyleResponse = await fetch(normalizedBaseStyleUrl, { credentials: 'omit' });
|
||||||
|
|
||||||
|
if (!baseStyleResponse.ok) {
|
||||||
|
throw new Error(`Failed to load base map style: ${baseStyleResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseStyle = await baseStyleResponse.json();
|
||||||
|
const baseSources = baseStyle?.sources && 'object' === typeof baseStyle.sources
|
||||||
|
? baseStyle.sources
|
||||||
|
: {};
|
||||||
|
const protomapsSource = baseSources[PROTOMAPS_SOURCE_ID] && 'object' === typeof baseSources[PROTOMAPS_SOURCE_ID]
|
||||||
|
? baseSources[PROTOMAPS_SOURCE_ID]
|
||||||
|
: {
|
||||||
|
type: 'vector',
|
||||||
|
attribution: PROTOMAPS_ATTRIBUTION,
|
||||||
|
url: PROTOMAPS_PM_TILES_URL,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const basemapsApi = await getBasemapsApi();
|
||||||
|
const flavorResponse = await fetch(PROTOMAPS_FLAVOR_URL, { credentials: 'omit' });
|
||||||
|
|
||||||
|
if (!flavorResponse.ok) {
|
||||||
|
throw new Error(`Failed to load Protomaps flavor: ${flavorResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const flavor = await flavorResponse.json();
|
||||||
|
const generatedLayers = basemapsApi.layers(PROTOMAPS_SOURCE_ID, flavor, { lang: 'de' });
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
version: 8,
|
||||||
|
glyphs: baseStyle?.glyphs || PROTOMAPS_GLYPHS_URL,
|
||||||
|
sprite: baseStyle?.sprite || PROTOMAPS_SPRITE_URL,
|
||||||
|
sources: {
|
||||||
|
...baseSources,
|
||||||
|
[PROTOMAPS_SOURCE_ID]: protomapsSource,
|
||||||
|
},
|
||||||
|
layers: generatedLayers,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return baseStyle;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const cachedStylePromise = stylePromise.catch((error) => {
|
||||||
|
protomapsStylePromises.delete(normalizedBaseStyleUrl);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
protomapsStylePromises.set(normalizedBaseStyleUrl, cachedStylePromise);
|
||||||
|
|
||||||
|
return cachedStylePromise;
|
||||||
|
};
|
||||||
|
|
||||||
const rewriteGlyphUrl = (url) => {
|
const rewriteGlyphUrl = (url) => {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return url;
|
return url;
|
||||||
@@ -118,6 +238,21 @@ const escapeHtml = (value) => {
|
|||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const decodeHtmlEntities = (value) => {
|
||||||
|
const stringValue = String(value ?? '');
|
||||||
|
|
||||||
|
if ('undefined' === typeof document || !document.createElement) {
|
||||||
|
return stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.innerHTML = stringValue;
|
||||||
|
|
||||||
|
return textarea.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizeDisplayText = (value) => escapeHtml(decodeHtmlEntities(value));
|
||||||
|
|
||||||
const parseItems = (container) => {
|
const parseItems = (container) => {
|
||||||
const dataElementId = container.dataset.mapDataId || '';
|
const dataElementId = container.dataset.mapDataId || '';
|
||||||
|
|
||||||
@@ -141,7 +276,7 @@ const parseItems = (container) => {
|
|||||||
const unique = new Map();
|
const unique = new Map();
|
||||||
|
|
||||||
parsed.forEach((item) => {
|
parsed.forEach((item) => {
|
||||||
if (!item || !['event', 'location', 'organisation'].includes(item.type)) {
|
if (!item || !['event', 'organisation'].includes(item.type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,9 +411,7 @@ const ensureSatelliteLayer = (map) => {
|
|||||||
|
|
||||||
const markerLayerCandidates = [
|
const markerLayerCandidates = [
|
||||||
EVENT_CLUSTER_LAYER_ID,
|
EVENT_CLUSTER_LAYER_ID,
|
||||||
EVENT_CLUSTER_SPIDERFY_LAYER_ID,
|
|
||||||
EVENT_UNCLUSTERED_LAYER_ID,
|
EVENT_UNCLUSTERED_LAYER_ID,
|
||||||
'eventmanager-non-events',
|
|
||||||
];
|
];
|
||||||
const beforeId = markerLayerCandidates.find((layerId) => map.getLayer(layerId));
|
const beforeId = markerLayerCandidates.find((layerId) => map.getLayer(layerId));
|
||||||
|
|
||||||
@@ -297,6 +430,44 @@ const ensureSatelliteLayer = (map) => {
|
|||||||
'raster-saturation': 0,
|
'raster-saturation': 0,
|
||||||
},
|
},
|
||||||
}, beforeId);
|
}, beforeId);
|
||||||
|
|
||||||
|
map.addSource(SATELLITE_ROADS_SOURCE_ID, {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: [SATELLITE_ROADS_TILE_URL],
|
||||||
|
tileSize: 256,
|
||||||
|
attribution: SATELLITE_ATTRIBUTION,
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addLayer({
|
||||||
|
id: SATELLITE_ROADS_LAYER_ID,
|
||||||
|
type: 'raster',
|
||||||
|
source: SATELLITE_ROADS_SOURCE_ID,
|
||||||
|
layout: {
|
||||||
|
visibility: 'none',
|
||||||
|
},
|
||||||
|
paint: {
|
||||||
|
'raster-opacity': 1,
|
||||||
|
},
|
||||||
|
}, beforeId);
|
||||||
|
|
||||||
|
map.addSource(SATELLITE_LABELS_SOURCE_ID, {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: [SATELLITE_LABELS_TILE_URL],
|
||||||
|
tileSize: 256,
|
||||||
|
attribution: SATELLITE_ATTRIBUTION,
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addLayer({
|
||||||
|
id: SATELLITE_LABELS_LAYER_ID,
|
||||||
|
type: 'raster',
|
||||||
|
source: SATELLITE_LABELS_SOURCE_ID,
|
||||||
|
layout: {
|
||||||
|
visibility: 'none',
|
||||||
|
},
|
||||||
|
paint: {
|
||||||
|
'raster-opacity': 1,
|
||||||
|
},
|
||||||
|
}, beforeId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setSatelliteMode = (map, enabled) => {
|
const setSatelliteMode = (map, enabled) => {
|
||||||
@@ -310,7 +481,16 @@ const setSatelliteMode = (map, enabled) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hybridOverlayLayerIds = [SATELLITE_ROADS_LAYER_ID, SATELLITE_LABELS_LAYER_ID];
|
||||||
|
|
||||||
map.setLayoutProperty(SATELLITE_LAYER_ID, 'visibility', enabled ? 'visible' : 'none');
|
map.setLayoutProperty(SATELLITE_LAYER_ID, 'visibility', enabled ? 'visible' : 'none');
|
||||||
|
hybridOverlayLayerIds.forEach((layerId) => {
|
||||||
|
if (map.getLayer(layerId)) {
|
||||||
|
map.setLayoutProperty(layerId, 'visibility', enabled ? 'visible' : 'none');
|
||||||
|
map.setPaintProperty(layerId, 'raster-opacity-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||||
|
map.setPaintProperty(layerId, 'raster-opacity', enabled ? 1 : 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-opacity-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-opacity-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-brightness-min-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-brightness-min-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-brightness-max-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-brightness-max-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||||
@@ -406,17 +586,38 @@ const ensureMarkerImage = (map, color, prefix = 'eventmanager-marker', options =
|
|||||||
|
|
||||||
const popupHtmlFor = (item) => {
|
const popupHtmlFor = (item) => {
|
||||||
if (item.type === 'event') {
|
if (item.type === 'event') {
|
||||||
const locationTitle = escapeHtml(item.extra.locationTitle || '');
|
const title = sanitizeDisplayText(item.title || '');
|
||||||
const startDate = escapeHtml(item.extra.startDate || '');
|
const locationTitle = sanitizeDisplayText(item.extra.locationTitle || '');
|
||||||
|
const startDate = sanitizeDisplayText(item.extra.startDate || '');
|
||||||
|
const detailUrl = String(item.extra.detailUrl || '').trim();
|
||||||
|
const hasDetailUrl = /^(https?:\/\/|\/)/i.test(detailUrl);
|
||||||
|
const detailLink = hasDetailUrl
|
||||||
|
? `<div><a href="${escapeHtml(detailUrl)}">mehr Infos</a></div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
return [
|
return [
|
||||||
`<strong>${escapeHtml(item.title)}</strong>`,
|
`<strong>${title}</strong>`,
|
||||||
locationTitle ? `<div>${locationTitle}</div>` : '',
|
locationTitle ? `<div>${locationTitle}</div>` : '',
|
||||||
startDate ? `<div>${startDate}</div>` : '',
|
startDate ? `<div>${startDate}</div>` : '',
|
||||||
|
detailLink,
|
||||||
].join('');
|
].join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<strong>${escapeHtml(item.title)}</strong>`;
|
if (item.type === 'organisation') {
|
||||||
|
const title = sanitizeDisplayText(item.title || '');
|
||||||
|
const detailUrl = String(item.extra?.detailUrl || '').trim();
|
||||||
|
const hasDetailUrl = /^(https?:\/\/|\/)/i.test(detailUrl);
|
||||||
|
const detailLink = hasDetailUrl
|
||||||
|
? `<div><a href="${escapeHtml(detailUrl)}">mehr Infos</a></div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
`<strong>${title}</strong>`,
|
||||||
|
detailLink,
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<strong>${sanitizeDisplayText(item.title)}</strong>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toFeature = (item) => ({
|
const toFeature = (item) => ({
|
||||||
@@ -432,11 +633,12 @@ const toFeature = (item) => ({
|
|||||||
markerTagIds: Array.isArray(item.extra?.organizationTagIds) ? item.extra.organizationTagIds : [],
|
markerTagIds: Array.isArray(item.extra?.organizationTagIds) ? item.extra.organizationTagIds : [],
|
||||||
markerTagLabels: Array.isArray(item.extra?.organizationTagLabels) ? item.extra.organizationTagLabels : [],
|
markerTagLabels: Array.isArray(item.extra?.organizationTagLabels) ? item.extra.organizationTagLabels : [],
|
||||||
title: item.title,
|
title: item.title,
|
||||||
|
latitude: item.latitude,
|
||||||
popupHtml: popupHtmlFor(item),
|
popupHtml: popupHtmlFor(item),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const initOrganizationMarkers = (map, organizationItems, organizationColor) => {
|
const initOrganizationMarkers = (map, organizationItems, organizationColor, openPopup) => {
|
||||||
if (!organizationItems.length) {
|
if (!organizationItems.length) {
|
||||||
return {
|
return {
|
||||||
setOnlyTagId: () => {},
|
setOnlyTagId: () => {},
|
||||||
@@ -445,17 +647,30 @@ const initOrganizationMarkers = (map, organizationItems, organizationColor) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const markerEntries = organizationItems.map((item) => {
|
const sortedOrganizationItems = [...organizationItems].sort((firstItem, secondItem) => secondItem.latitude - firstItem.latitude);
|
||||||
|
|
||||||
|
const markerEntries = sortedOrganizationItems.map((item) => {
|
||||||
const popupHtml = popupHtmlFor(item);
|
const popupHtml = popupHtmlFor(item);
|
||||||
const marker = new maplibregl.Marker(organizationColor ? { color: organizationColor } : undefined)
|
const marker = new maplibregl.Marker(organizationColor ? { color: organizationColor } : undefined)
|
||||||
.setLngLat([item.longitude, item.latitude]);
|
.setLngLat([item.longitude, item.latitude]);
|
||||||
|
|
||||||
if (popupHtml) {
|
|
||||||
marker.setPopup(new maplibregl.Popup({ offset: 15 }).setHTML(String(popupHtml)));
|
|
||||||
}
|
|
||||||
|
|
||||||
marker.addTo(map);
|
marker.addTo(map);
|
||||||
|
|
||||||
|
const markerElement = marker.getElement();
|
||||||
|
|
||||||
|
if (markerElement) {
|
||||||
|
markerElement.style.zIndex = String(Math.round((90 - item.latitude) * 1000));
|
||||||
|
markerElement.style.cursor = 'pointer';
|
||||||
|
|
||||||
|
if (popupHtml && typeof openPopup === 'function') {
|
||||||
|
markerElement.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
openPopup([item.longitude, item.latitude], String(popupHtml));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
marker,
|
marker,
|
||||||
tagIds: normalizeTagIds(item.extra?.organizationTagIds),
|
tagIds: normalizeTagIds(item.extra?.organizationTagIds),
|
||||||
@@ -507,7 +722,7 @@ const initOrganizationMarkers = (map, organizationItems, organizationColor) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const bindExternalTagFilters = (container, map, organizationMarkerManager, eventLayerManager) => {
|
const bindExternalTagFilters = (container, map, organizationMarkerManager, eventLayerManager, closePopup) => {
|
||||||
const wrapperId = container.dataset.mapFilterWrapperId || '';
|
const wrapperId = container.dataset.mapFilterWrapperId || '';
|
||||||
|
|
||||||
if (!wrapperId || !organizationMarkerManager) {
|
if (!wrapperId || !organizationMarkerManager) {
|
||||||
@@ -532,6 +747,7 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event
|
|||||||
const eventToggleButton = wrapper.querySelector('[data-map-event-toggle="1"]');
|
const eventToggleButton = wrapper.querySelector('[data-map-event-toggle="1"]');
|
||||||
const canFilterByTag = tagButtons.length > 0;
|
const canFilterByTag = tagButtons.length > 0;
|
||||||
const canFilterByEvents = !!eventToggleButton;
|
const canFilterByEvents = !!eventToggleButton;
|
||||||
|
const canToggleEvents = !!(eventToggleButton && eventLayerManager);
|
||||||
|
|
||||||
const setEventButtonState = (isActive) => {
|
const setEventButtonState = (isActive) => {
|
||||||
if (!eventToggleButton) {
|
if (!eventToggleButton) {
|
||||||
@@ -549,6 +765,26 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event
|
|||||||
let activeTagId = null;
|
let activeTagId = null;
|
||||||
let eventsOnly = false;
|
let eventsOnly = false;
|
||||||
|
|
||||||
|
const updateExclusiveButtonAccessibility = () => {
|
||||||
|
tagButtons.forEach((button) => {
|
||||||
|
const isActive = button.getAttribute('aria-pressed') === 'true';
|
||||||
|
button.setAttribute('aria-disabled', isActive ? 'true' : 'false');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!eventToggleButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canToggleEvents) {
|
||||||
|
eventToggleButton.setAttribute('aria-disabled', 'true');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEventActive = eventToggleButton.getAttribute('aria-pressed') === 'true';
|
||||||
|
eventToggleButton.setAttribute('aria-disabled', isEventActive ? 'true' : 'false');
|
||||||
|
};
|
||||||
|
|
||||||
const applyFilter = () => {
|
const applyFilter = () => {
|
||||||
if (eventsOnly) {
|
if (eventsOnly) {
|
||||||
organizationMarkerManager.setVisible(false);
|
organizationMarkerManager.setVisible(false);
|
||||||
@@ -581,14 +817,17 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event
|
|||||||
|
|
||||||
tagButtons.forEach((button) => {
|
tagButtons.forEach((button) => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
|
if (button.getAttribute('aria-disabled') === 'true') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const clickedTagId = String(button.dataset.mapTagFilter || '').trim();
|
const clickedTagId = String(button.dataset.mapTagFilter || '').trim();
|
||||||
|
|
||||||
if (!/^\d+$/.test(clickedTagId)) {
|
if (!/^\d+$/.test(clickedTagId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isActive = button.getAttribute('aria-pressed') === 'true';
|
activeTagId = clickedTagId;
|
||||||
activeTagId = isActive ? null : clickedTagId;
|
|
||||||
eventsOnly = false;
|
eventsOnly = false;
|
||||||
|
|
||||||
tagButtons.forEach((otherButton) => {
|
tagButtons.forEach((otherButton) => {
|
||||||
@@ -597,6 +836,10 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event
|
|||||||
});
|
});
|
||||||
|
|
||||||
setEventButtonState(false);
|
setEventButtonState(false);
|
||||||
|
updateExclusiveButtonAccessibility();
|
||||||
|
if (typeof closePopup === 'function') {
|
||||||
|
closePopup();
|
||||||
|
}
|
||||||
applyFilter();
|
applyFilter();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -628,12 +871,19 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event
|
|||||||
|
|
||||||
if (eventToggleButton && eventLayerManager) {
|
if (eventToggleButton && eventLayerManager) {
|
||||||
eventToggleButton.addEventListener('click', () => {
|
eventToggleButton.addEventListener('click', () => {
|
||||||
const isActive = eventToggleButton.getAttribute('aria-pressed') === 'true';
|
if (eventToggleButton.getAttribute('aria-disabled') === 'true') {
|
||||||
eventsOnly = !isActive;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventsOnly = true;
|
||||||
activeTagId = null;
|
activeTagId = null;
|
||||||
|
|
||||||
tagButtons.forEach((button) => setButtonState(button, false));
|
tagButtons.forEach((button) => setButtonState(button, false));
|
||||||
setEventButtonState(eventsOnly);
|
setEventButtonState(eventsOnly);
|
||||||
|
updateExclusiveButtonAccessibility();
|
||||||
|
if (typeof closePopup === 'function') {
|
||||||
|
closePopup();
|
||||||
|
}
|
||||||
applyFilter();
|
applyFilter();
|
||||||
});
|
});
|
||||||
} else if (eventToggleButton) {
|
} else if (eventToggleButton) {
|
||||||
@@ -711,139 +961,21 @@ const bindExternalTagFilters = (container, map, organizationMarkerManager, event
|
|||||||
setButtonState(button, null !== activeTagId && tagId === activeTagId);
|
setButtonState(button, null !== activeTagId && tagId === activeTagId);
|
||||||
});
|
});
|
||||||
setEventButtonState(eventsOnly);
|
setEventButtonState(eventsOnly);
|
||||||
|
updateExclusiveButtonAccessibility();
|
||||||
};
|
};
|
||||||
|
|
||||||
applyInitialDisplayMode();
|
applyInitialDisplayMode();
|
||||||
|
updateExclusiveButtonAccessibility();
|
||||||
applyFilter();
|
applyFilter();
|
||||||
applyMapStyleMode();
|
applyMapStyleMode();
|
||||||
};
|
};
|
||||||
|
|
||||||
const initLocationLayers = (map, locationItems, markerImageId) => {
|
const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMarkerImageId, openPopup, closePopup) => {
|
||||||
if (!locationItems.length) {
|
|
||||||
return {
|
|
||||||
setVisible: () => {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceId = 'eventmanager-non-events-source';
|
|
||||||
const layerId = 'eventmanager-non-events';
|
|
||||||
map.addSource(sourceId, {
|
|
||||||
type: 'geojson',
|
|
||||||
data: {
|
|
||||||
type: 'FeatureCollection',
|
|
||||||
features: locationItems.map(toFeature),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
map.addLayer({
|
|
||||||
id: layerId,
|
|
||||||
type: 'symbol',
|
|
||||||
source: sourceId,
|
|
||||||
layout: {
|
|
||||||
'icon-image': markerImageId,
|
|
||||||
'icon-size': 1,
|
|
||||||
'icon-anchor': 'bottom',
|
|
||||||
'icon-allow-overlap': true,
|
|
||||||
'icon-ignore-placement': true,
|
|
||||||
},
|
|
||||||
paint: {
|
|
||||||
'icon-opacity': 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on('click', layerId, (event) => {
|
|
||||||
const blockingLayers = [
|
|
||||||
EVENT_CLUSTER_LAYER_ID,
|
|
||||||
EVENT_CLUSTER_SPIDERFY_LAYER_ID,
|
|
||||||
EVENT_UNCLUSTERED_LAYER_ID,
|
|
||||||
].filter((id) => map.getLayer(id));
|
|
||||||
|
|
||||||
if (blockingLayers.length) {
|
|
||||||
const topEventFeatures = map.queryRenderedFeatures(event.point, { layers: blockingLayers });
|
|
||||||
|
|
||||||
if (topEventFeatures.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const feature = event.features && event.features[0] ? event.features[0] : null;
|
|
||||||
|
|
||||||
if (!feature) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const popupHtml = feature?.properties?.popupHtml || '';
|
|
||||||
const coordinates = event.lngLat ? [event.lngLat.lng, event.lngLat.lat] : null;
|
|
||||||
|
|
||||||
if (!coordinates) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
new maplibregl.Popup({ offset: 15 })
|
|
||||||
.setLngLat(coordinates)
|
|
||||||
.setHTML(String(popupHtml))
|
|
||||||
.addTo(map);
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on('mouseenter', layerId, () => {
|
|
||||||
map.getCanvas().style.cursor = 'pointer';
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on('mouseleave', layerId, () => {
|
|
||||||
map.getCanvas().style.cursor = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
const setLayerLayoutVisibility = (isVisible) => {
|
|
||||||
if (!map.getLayer(layerId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
map.setLayoutProperty(layerId, 'visibility', isVisible ? 'visible' : 'none');
|
|
||||||
};
|
|
||||||
|
|
||||||
const fadeLayerOpacity = (value) => {
|
|
||||||
if (!map.getLayer(layerId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
map.setPaintProperty(layerId, 'icon-opacity-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
|
||||||
map.setPaintProperty(layerId, 'icon-opacity', value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
setVisible: (isVisible) => {
|
|
||||||
if (isVisible) {
|
|
||||||
setLayerLayoutVisibility(true);
|
|
||||||
fadeLayerOpacity(1);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fadeLayerOpacity(0);
|
|
||||||
|
|
||||||
window.setTimeout(() => {
|
|
||||||
setLayerLayoutVisibility(false);
|
|
||||||
}, EVENT_FADE_DURATION_MS + 10);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMarkerImageId) => {
|
|
||||||
const sourceId = 'eventmanager-events-source';
|
const sourceId = 'eventmanager-events-source';
|
||||||
const clusterLayerId = EVENT_CLUSTER_LAYER_ID;
|
const clusterLayerId = EVENT_CLUSTER_LAYER_ID;
|
||||||
const clusterSpiderfyLayerId = EVENT_CLUSTER_SPIDERFY_LAYER_ID;
|
|
||||||
const unclusteredLayerId = EVENT_UNCLUSTERED_LAYER_ID;
|
const unclusteredLayerId = EVENT_UNCLUSTERED_LAYER_ID;
|
||||||
const spiderfyHitAreaImageId = 'eventmanager-spiderfy-hit-area';
|
|
||||||
const features = eventItems.map(toFeature);
|
const features = eventItems.map(toFeature);
|
||||||
|
|
||||||
if (!map.hasImage(spiderfyHitAreaImageId)) {
|
|
||||||
map.addImage(spiderfyHitAreaImageId, {
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
data: new Uint8Array([0, 0, 0, 0]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
map.addSource(sourceId, {
|
map.addSource(sourceId, {
|
||||||
type: 'geojson',
|
type: 'geojson',
|
||||||
data: {
|
data: {
|
||||||
@@ -875,25 +1007,10 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark
|
|||||||
},
|
},
|
||||||
paint: {
|
paint: {
|
||||||
'icon-opacity': 1,
|
'icon-opacity': 1,
|
||||||
|
'icon-translate': [0, 4],
|
||||||
'text-color': '#FFFFFF',
|
'text-color': '#FFFFFF',
|
||||||
'text-opacity': 1,
|
'text-opacity': 1,
|
||||||
},
|
'text-translate': [0, 4],
|
||||||
});
|
|
||||||
|
|
||||||
map.addLayer({
|
|
||||||
id: clusterSpiderfyLayerId,
|
|
||||||
type: 'symbol',
|
|
||||||
source: sourceId,
|
|
||||||
filter: ['has', 'point_count'],
|
|
||||||
layout: {
|
|
||||||
'icon-image': spiderfyHitAreaImageId,
|
|
||||||
'icon-size': 36,
|
|
||||||
'icon-anchor': 'bottom',
|
|
||||||
'icon-offset': [0, -1.0],
|
|
||||||
'icon-allow-overlap': true,
|
|
||||||
},
|
|
||||||
paint: {
|
|
||||||
'icon-opacity': 0,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -908,9 +1025,11 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark
|
|||||||
'icon-anchor': 'bottom',
|
'icon-anchor': 'bottom',
|
||||||
'icon-allow-overlap': true,
|
'icon-allow-overlap': true,
|
||||||
'icon-ignore-placement': true,
|
'icon-ignore-placement': true,
|
||||||
|
'symbol-sort-key': ['*', -1, ['to-number', ['get', 'latitude'], 0]],
|
||||||
},
|
},
|
||||||
paint: {
|
paint: {
|
||||||
'icon-opacity': 1,
|
'icon-opacity': 1,
|
||||||
|
'icon-translate': [0, 4],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -928,10 +1047,9 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new maplibregl.Popup({ offset: 15 })
|
if (typeof openPopup === 'function') {
|
||||||
.setLngLat([lng, lat])
|
openPopup([lng, lat], String(popupHtml));
|
||||||
.setHTML(String(popupHtml))
|
}
|
||||||
.addTo(map);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
map.on('click', unclusteredLayerId, (event) => {
|
map.on('click', unclusteredLayerId, (event) => {
|
||||||
@@ -1016,7 +1134,7 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark
|
|||||||
},
|
},
|
||||||
spiderLeavesPaint: {
|
spiderLeavesPaint: {
|
||||||
'icon-opacity': 1,
|
'icon-opacity': 1,
|
||||||
'icon-translate': [0, 8],
|
'icon-translate': [-3, 9],
|
||||||
},
|
},
|
||||||
onLeafClick: (feature, spiderEvent) => {
|
onLeafClick: (feature, spiderEvent) => {
|
||||||
const clickCoordinates = spiderEvent?.lngLat
|
const clickCoordinates = spiderEvent?.lngLat
|
||||||
@@ -1048,14 +1166,6 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark
|
|||||||
map.getCanvas().style.cursor = '';
|
map.getCanvas().style.cursor = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
const setLayerLayoutVisibility = (layerId, isVisible) => {
|
|
||||||
if (!map.getLayer(layerId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
map.setLayoutProperty(layerId, 'visibility', isVisible ? 'visible' : 'none');
|
|
||||||
};
|
|
||||||
|
|
||||||
const fadeLayerOpacity = (layerId, property, value, duration = EVENT_FADE_DURATION_MS) => {
|
const fadeLayerOpacity = (layerId, property, value, duration = EVENT_FADE_DURATION_MS) => {
|
||||||
if (!map.getLayer(layerId)) {
|
if (!map.getLayer(layerId)) {
|
||||||
return;
|
return;
|
||||||
@@ -1067,14 +1177,31 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark
|
|||||||
|
|
||||||
const eventLayerIds = [
|
const eventLayerIds = [
|
||||||
clusterLayerId,
|
clusterLayerId,
|
||||||
clusterSpiderfyLayerId,
|
|
||||||
unclusteredLayerId,
|
unclusteredLayerId,
|
||||||
];
|
];
|
||||||
|
const clusterFilter = ['has', 'point_count'];
|
||||||
|
const unclusteredFilter = ['!', ['has', 'point_count']];
|
||||||
|
const hiddenFilter = ['has', '__eventmanager_hidden__'];
|
||||||
|
let hideLayersTimeoutId = null;
|
||||||
|
|
||||||
|
const setLayerFilter = (layerId, filter) => {
|
||||||
|
if (!map.getLayer(layerId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.setFilter(layerId, filter);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setVisible: (isVisible) => {
|
setVisible: (isVisible) => {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
eventLayerIds.forEach((layerId) => setLayerLayoutVisibility(layerId, true));
|
if (null !== hideLayersTimeoutId) {
|
||||||
|
window.clearTimeout(hideLayersTimeoutId);
|
||||||
|
hideLayersTimeoutId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLayerFilter(clusterLayerId, clusterFilter);
|
||||||
|
setLayerFilter(unclusteredLayerId, unclusteredFilter);
|
||||||
|
|
||||||
fadeLayerOpacity(clusterLayerId, 'icon-opacity', 1);
|
fadeLayerOpacity(clusterLayerId, 'icon-opacity', 1);
|
||||||
fadeLayerOpacity(clusterLayerId, 'text-opacity', 1);
|
fadeLayerOpacity(clusterLayerId, 'text-opacity', 1);
|
||||||
@@ -1083,16 +1210,26 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spiderfyInstance && typeof spiderfyInstance.unspiderfyAll === 'function') {
|
if (spiderfyInstance) {
|
||||||
spiderfyInstance.unspiderfyAll();
|
if (typeof spiderfyInstance._clearSpiderifiedCluster === 'function') {
|
||||||
|
spiderfyInstance._clearSpiderifiedCluster();
|
||||||
|
} else if (typeof spiderfyInstance.unspiderfyAll === 'function') {
|
||||||
|
spiderfyInstance.unspiderfyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof closePopup === 'function') {
|
||||||
|
closePopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
fadeLayerOpacity(clusterLayerId, 'icon-opacity', 0);
|
fadeLayerOpacity(clusterLayerId, 'icon-opacity', 0);
|
||||||
fadeLayerOpacity(clusterLayerId, 'text-opacity', 0);
|
fadeLayerOpacity(clusterLayerId, 'text-opacity', 0);
|
||||||
fadeLayerOpacity(unclusteredLayerId, 'icon-opacity', 0);
|
fadeLayerOpacity(unclusteredLayerId, 'icon-opacity', 0);
|
||||||
|
|
||||||
window.setTimeout(() => {
|
hideLayersTimeoutId = window.setTimeout(() => {
|
||||||
eventLayerIds.forEach((layerId) => setLayerLayoutVisibility(layerId, false));
|
setLayerFilter(clusterLayerId, hiddenFilter);
|
||||||
|
setLayerFilter(unclusteredLayerId, hiddenFilter);
|
||||||
|
hideLayersTimeoutId = null;
|
||||||
}, EVENT_FADE_DURATION_MS + 10);
|
}, EVENT_FADE_DURATION_MS + 10);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1109,8 +1246,12 @@ const initSingleMap = (container) => {
|
|||||||
const customLat = parseCoordinate(container.dataset.mapCenterLat);
|
const customLat = parseCoordinate(container.dataset.mapCenterLat);
|
||||||
const customLng = parseCoordinate(container.dataset.mapCenterLng);
|
const customLng = parseCoordinate(container.dataset.mapCenterLng);
|
||||||
const customZoom = Number(container.dataset.mapCenterZoom);
|
const customZoom = Number(container.dataset.mapCenterZoom);
|
||||||
|
const customPitch = Number(container.dataset.mapPitch);
|
||||||
const eventColor = normalizeHexColor(container.dataset.mapEventColor, DEFAULT_EVENT_COLOR);
|
const eventColor = normalizeHexColor(container.dataset.mapEventColor, DEFAULT_EVENT_COLOR);
|
||||||
const hasCustomCenter = null !== customLat && null !== customLng;
|
const hasCustomCenter = null !== customLat && null !== customLng;
|
||||||
|
const initialPitch = Number.isFinite(customPitch)
|
||||||
|
? Math.max(0, Math.min(85, customPitch))
|
||||||
|
: 0;
|
||||||
|
|
||||||
let initialCenter = DEFAULT_CENTER;
|
let initialCenter = DEFAULT_CENTER;
|
||||||
let initialZoom = DEFAULT_ZOOM;
|
let initialZoom = DEFAULT_ZOOM;
|
||||||
@@ -1126,7 +1267,7 @@ const initSingleMap = (container) => {
|
|||||||
container.dataset.mapInitialized = '1';
|
container.dataset.mapInitialized = '1';
|
||||||
|
|
||||||
ensureDependencies()
|
ensureDependencies()
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
if (typeof maplibregl === 'undefined') {
|
if (typeof maplibregl === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1137,12 +1278,13 @@ const initSingleMap = (container) => {
|
|||||||
window.__eventmanagerPmtilesRegistered = true;
|
window.__eventmanagerPmtilesRegistered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = container.dataset.mapStyle || 'https://maps.mummert.media/metadaten/world-light.json';
|
const baseStyleUrl = container.dataset.mapStyle || DEFAULT_BASE_STYLE_URL;
|
||||||
|
const style = await getProtomapsStyle(baseStyleUrl);
|
||||||
const map = new maplibregl.Map({
|
const map = new maplibregl.Map({
|
||||||
container,
|
container,
|
||||||
style,
|
style,
|
||||||
zoom: initialZoom,
|
zoom: initialZoom,
|
||||||
pitch: 0,
|
pitch: initialPitch,
|
||||||
bearing: 0,
|
bearing: 0,
|
||||||
center: initialCenter,
|
center: initialCenter,
|
||||||
transformRequest: (url, resourceType) => {
|
transformRequest: (url, resourceType) => {
|
||||||
@@ -1158,6 +1300,44 @@ const initSingleMap = (container) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let activePopup = null;
|
||||||
|
|
||||||
|
const closePopup = () => {
|
||||||
|
if (!activePopup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activePopup.remove();
|
||||||
|
activePopup = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openPopup = (coordinates, popupHtml) => {
|
||||||
|
if (!Array.isArray(coordinates) || coordinates.length !== 2 || !popupHtml) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [lng, lat] = coordinates.map((value) => Number(value));
|
||||||
|
|
||||||
|
if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closePopup();
|
||||||
|
|
||||||
|
const popup = new maplibregl.Popup({ offset: 15 })
|
||||||
|
.setLngLat([lng, lat])
|
||||||
|
.setHTML(String(popupHtml))
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
|
popup.on('close', () => {
|
||||||
|
if (activePopup === popup) {
|
||||||
|
activePopup = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
activePopup = popup;
|
||||||
|
};
|
||||||
|
|
||||||
map.on('style.load', () => {
|
map.on('style.load', () => {
|
||||||
applyDefaultProjection(map);
|
applyDefaultProjection(map);
|
||||||
});
|
});
|
||||||
@@ -1204,9 +1384,12 @@ const initSingleMap = (container) => {
|
|||||||
map.on('load', async () => {
|
map.on('load', async () => {
|
||||||
applyDefaultProjection(map);
|
applyDefaultProjection(map);
|
||||||
|
|
||||||
const organizationItems = items.filter((item) => item.type === 'organisation');
|
const organizationItems = items
|
||||||
const locationItems = items.filter((item) => item.type === 'location');
|
.filter((item) => item.type === 'organisation')
|
||||||
const eventItems = items.filter((item) => item.type === 'event');
|
.sort((firstItem, secondItem) => secondItem.latitude - firstItem.latitude);
|
||||||
|
const eventItems = items
|
||||||
|
.filter((item) => item.type === 'event')
|
||||||
|
.sort((firstItem, secondItem) => secondItem.latitude - firstItem.latitude);
|
||||||
const allCoordinates = items.map((item) => [item.longitude, item.latitude]);
|
const allCoordinates = items.map((item) => [item.longitude, item.latitude]);
|
||||||
const organizationColor = normalizeHexColor(
|
const organizationColor = normalizeHexColor(
|
||||||
container.dataset.mapOrganizationColor,
|
container.dataset.mapOrganizationColor,
|
||||||
@@ -1224,25 +1407,14 @@ const initSingleMap = (container) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const organizationMarkerManager = initOrganizationMarkers(map, organizationItems, organizationColor);
|
const organizationMarkerManager = initOrganizationMarkers(map, organizationItems, organizationColor, openPopup);
|
||||||
const locationLayerManager = initLocationLayers(map, locationItems, eventMarkerImageId);
|
|
||||||
let eventLayerManager = null;
|
let eventLayerManager = null;
|
||||||
|
|
||||||
if (eventItems.length) {
|
if (eventItems.length) {
|
||||||
eventLayerManager = initEventLayers(map, eventItems, eventColor, eventMarkerImageId, clusterMarkerImageId);
|
eventLayerManager = initEventLayers(map, eventItems, eventColor, eventMarkerImageId, clusterMarkerImageId, openPopup, closePopup);
|
||||||
}
|
}
|
||||||
|
|
||||||
const combinedEventLayerManager = {
|
bindExternalTagFilters(container, map, organizationMarkerManager, eventLayerManager, closePopup);
|
||||||
setVisible: (isVisible) => {
|
|
||||||
locationLayerManager.setVisible(isVisible);
|
|
||||||
|
|
||||||
if (eventLayerManager) {
|
|
||||||
eventLayerManager.setVisible(isVisible);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
bindExternalTagFilters(container, map, organizationMarkerManager, combinedEventLayerManager);
|
|
||||||
|
|
||||||
if ('markers' === centerMode) {
|
if ('markers' === centerMode) {
|
||||||
fitBounds(allCoordinates);
|
fitBounds(allCoordinates);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
})();
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
(function () {
|
||||||
|
const mounts = document.querySelectorAll('[data-mm-editor="tiptap"]');
|
||||||
|
|
||||||
|
if (!mounts.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const runFallback = (mount) => {
|
||||||
|
if (!mount || mount.querySelector('.ProseMirror') || mount.getAttribute('data-mm-fallback-ready') === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textareaId = mount.getAttribute('data-textarea-id');
|
||||||
|
|
||||||
|
if (!textareaId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textarea = document.getElementById(textareaId);
|
||||||
|
|
||||||
|
if (!textarea) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolbar = document.querySelector('[data-mm-editor-toolbar="' + textareaId + '"]');
|
||||||
|
const form = textarea.closest('form');
|
||||||
|
|
||||||
|
mount.setAttribute('contenteditable', 'true');
|
||||||
|
mount.setAttribute('data-mm-fallback-ready', '1');
|
||||||
|
mount.classList.add('mm-fallback-editor');
|
||||||
|
mount.innerHTML = textarea.value || '<p></p>';
|
||||||
|
textarea.style.display = 'none';
|
||||||
|
|
||||||
|
const syncState = () => {
|
||||||
|
textarea.value = mount.innerHTML;
|
||||||
|
};
|
||||||
|
|
||||||
|
const focusMount = () => {
|
||||||
|
mount.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
mount.addEventListener('input', syncState);
|
||||||
|
mount.addEventListener('blur', syncState);
|
||||||
|
|
||||||
|
if (toolbar) {
|
||||||
|
toolbar.addEventListener('click', (event) => {
|
||||||
|
const target = event.target.closest('button[data-action]');
|
||||||
|
|
||||||
|
if (!target || target.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const action = target.getAttribute('data-action');
|
||||||
|
|
||||||
|
focusMount();
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'paragraph':
|
||||||
|
document.execCommand('formatBlock', false, 'p');
|
||||||
|
break;
|
||||||
|
case 'h2':
|
||||||
|
document.execCommand('formatBlock', false, 'h2');
|
||||||
|
break;
|
||||||
|
case 'h3':
|
||||||
|
document.execCommand('formatBlock', false, 'h3');
|
||||||
|
break;
|
||||||
|
case 'bold':
|
||||||
|
document.execCommand('bold', false);
|
||||||
|
break;
|
||||||
|
case 'italic':
|
||||||
|
document.execCommand('italic', false);
|
||||||
|
break;
|
||||||
|
case 'underline':
|
||||||
|
document.execCommand('underline', false);
|
||||||
|
break;
|
||||||
|
case 'bulletList':
|
||||||
|
document.execCommand('insertUnorderedList', false);
|
||||||
|
break;
|
||||||
|
case 'orderedList':
|
||||||
|
document.execCommand('insertOrderedList', false);
|
||||||
|
break;
|
||||||
|
case 'indent':
|
||||||
|
document.execCommand('indent', false);
|
||||||
|
break;
|
||||||
|
case 'outdent':
|
||||||
|
document.execCommand('outdent', false);
|
||||||
|
break;
|
||||||
|
case 'undo':
|
||||||
|
document.execCommand('undo', false);
|
||||||
|
break;
|
||||||
|
case 'redo':
|
||||||
|
document.execCommand('redo', false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', syncState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
mounts.forEach(runFallback);
|
||||||
|
}, 900);
|
||||||
|
})();
|
||||||
@@ -0,0 +1,330 @@
|
|||||||
|
import { Editor } from 'https://esm.sh/@tiptap/core@3';
|
||||||
|
import Document from 'https://esm.sh/@tiptap/extension-document@3';
|
||||||
|
import Paragraph from 'https://esm.sh/@tiptap/extension-paragraph@3';
|
||||||
|
import Text from 'https://esm.sh/@tiptap/extension-text@3';
|
||||||
|
import Heading from 'https://esm.sh/@tiptap/extension-heading@3';
|
||||||
|
import Bold from 'https://esm.sh/@tiptap/extension-bold@3';
|
||||||
|
import Italic from 'https://esm.sh/@tiptap/extension-italic@3';
|
||||||
|
import Underline from 'https://esm.sh/@tiptap/extension-underline@3';
|
||||||
|
import BulletList from 'https://esm.sh/@tiptap/extension-bullet-list@3';
|
||||||
|
import OrderedList from 'https://esm.sh/@tiptap/extension-ordered-list@3';
|
||||||
|
import ListItem from 'https://esm.sh/@tiptap/extension-list-item@3';
|
||||||
|
import CharacterCount from 'https://esm.sh/@tiptap/extension-character-count@3';
|
||||||
|
import History from 'https://esm.sh/@tiptap/extension-history@3';
|
||||||
|
|
||||||
|
const CHARACTER_LIMIT = 30000;
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
const editorMounts = document.querySelectorAll('[data-mm-editor="tiptap"]');
|
||||||
|
|
||||||
|
if (!editorMounts.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncState = (editor, textarea) => {
|
||||||
|
textarea.value = editor.getHTML();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateEditorA11yState = (mount, textarea, textareaId, editor) => {
|
||||||
|
if (!mount || !textareaId || !editor || !textarea) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorNode = document.getElementById(`${textareaId}-errors`);
|
||||||
|
const hasError = !!(errorNode && errorNode.textContent && errorNode.textContent.trim().length > 0);
|
||||||
|
const plainTextLength = editor.storage?.characterCount?.characters?.() ?? 0;
|
||||||
|
const isEmpty = plainTextLength === 0;
|
||||||
|
const label = mount.getAttribute('data-editor-label') || textarea.getAttribute('aria-label') || 'Text';
|
||||||
|
|
||||||
|
mount.setAttribute('aria-invalid', hasError ? 'true' : 'false');
|
||||||
|
mount.setAttribute('aria-required', textarea.required ? 'true' : 'false');
|
||||||
|
|
||||||
|
const proseMirror = mount.querySelector('.ProseMirror');
|
||||||
|
|
||||||
|
if (!proseMirror) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
proseMirror.setAttribute('role', 'textbox');
|
||||||
|
proseMirror.setAttribute('aria-multiline', 'true');
|
||||||
|
proseMirror.setAttribute('aria-invalid', hasError ? 'true' : 'false');
|
||||||
|
proseMirror.setAttribute('aria-required', textarea.required ? 'true' : 'false');
|
||||||
|
proseMirror.setAttribute('aria-label', label);
|
||||||
|
proseMirror.setAttribute('data-empty', isEmpty ? 'true' : 'false');
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCharacterCounter = (counterNode, editor) => {
|
||||||
|
if (!counterNode || !editor?.storage?.characterCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const used = editor.storage.characterCount.characters();
|
||||||
|
const words = editor.storage.characterCount.words();
|
||||||
|
const remaining = CHARACTER_LIMIT - used;
|
||||||
|
|
||||||
|
counterNode.textContent = `${words.toLocaleString('de-DE')} Wörter · ${used.toLocaleString('de-DE')} / ${CHARACTER_LIMIT.toLocaleString('de-DE')} Zeichen`;
|
||||||
|
counterNode.classList.toggle('is-over-limit', remaining < 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupToolbarKeyboardNavigation = (toolbar) => {
|
||||||
|
if (!toolbar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEnabledButtons = () => Array.from(toolbar.querySelectorAll('button[data-action]:not(:disabled)'));
|
||||||
|
|
||||||
|
toolbar.addEventListener('keydown', (event) => {
|
||||||
|
const activeElement = event.target.closest('button[data-action]');
|
||||||
|
|
||||||
|
if (!activeElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttons = getEnabledButtons();
|
||||||
|
|
||||||
|
if (!buttons.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentIndex = buttons.indexOf(activeElement);
|
||||||
|
|
||||||
|
if (currentIndex < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextIndex = currentIndex;
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowRight':
|
||||||
|
nextIndex = (currentIndex + 1) % buttons.length;
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
nextIndex = (currentIndex - 1 + buttons.length) % buttons.length;
|
||||||
|
break;
|
||||||
|
case 'Home':
|
||||||
|
nextIndex = 0;
|
||||||
|
break;
|
||||||
|
case 'End':
|
||||||
|
nextIndex = buttons.length - 1;
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
case ' ':
|
||||||
|
event.preventDefault();
|
||||||
|
activeElement.click();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
buttons[nextIndex].focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateToolbarState = (toolbar, editor) => {
|
||||||
|
if (!toolbar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canIndent = editor.can().chain().focus().sinkListItem('listItem').run();
|
||||||
|
const canOutdent = editor.can().chain().focus().liftListItem('listItem').run();
|
||||||
|
const canUndo = editor.can().chain().focus().undo().run();
|
||||||
|
const canRedo = editor.can().chain().focus().redo().run();
|
||||||
|
|
||||||
|
toolbar.querySelectorAll('button[data-action]').forEach((button) => {
|
||||||
|
const action = button.getAttribute('data-action');
|
||||||
|
let isActive = false;
|
||||||
|
let isDisabled = false;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'paragraph':
|
||||||
|
isActive = editor.isActive('paragraph');
|
||||||
|
break;
|
||||||
|
case 'h2':
|
||||||
|
isActive = editor.isActive('heading', { level: 2 });
|
||||||
|
break;
|
||||||
|
case 'h3':
|
||||||
|
isActive = editor.isActive('heading', { level: 3 });
|
||||||
|
break;
|
||||||
|
case 'bold':
|
||||||
|
isActive = editor.isActive('bold');
|
||||||
|
break;
|
||||||
|
case 'italic':
|
||||||
|
isActive = editor.isActive('italic');
|
||||||
|
break;
|
||||||
|
case 'underline':
|
||||||
|
isActive = editor.isActive('underline');
|
||||||
|
break;
|
||||||
|
case 'bulletList':
|
||||||
|
isActive = editor.isActive('bulletList');
|
||||||
|
break;
|
||||||
|
case 'orderedList':
|
||||||
|
isActive = editor.isActive('orderedList');
|
||||||
|
break;
|
||||||
|
case 'indent':
|
||||||
|
isDisabled = !canIndent;
|
||||||
|
break;
|
||||||
|
case 'outdent':
|
||||||
|
isDisabled = !canOutdent;
|
||||||
|
break;
|
||||||
|
case 'undo':
|
||||||
|
isDisabled = !canUndo;
|
||||||
|
break;
|
||||||
|
case 'redo':
|
||||||
|
isDisabled = !canRedo;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.classList.toggle('is-active', isActive);
|
||||||
|
button.setAttribute('aria-pressed', isActive ? 'true' : 'false');
|
||||||
|
button.disabled = isDisabled;
|
||||||
|
button.setAttribute('aria-disabled', isDisabled ? 'true' : 'false');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
editorMounts.forEach((mount) => {
|
||||||
|
const textareaId = mount.getAttribute('data-textarea-id');
|
||||||
|
|
||||||
|
if (!textareaId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textarea = document.getElementById(textareaId);
|
||||||
|
|
||||||
|
if (!textarea) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.style.display = 'none';
|
||||||
|
|
||||||
|
const form = textarea.closest('form');
|
||||||
|
const toolbar = document.querySelector(`[data-mm-editor-toolbar="${textareaId}"]`);
|
||||||
|
const counterNode = document.querySelector(`[data-mm-editor-counter-for="${textareaId}"]`);
|
||||||
|
|
||||||
|
setupToolbarKeyboardNavigation(toolbar);
|
||||||
|
|
||||||
|
const editor = new Editor({
|
||||||
|
element: mount,
|
||||||
|
extensions: [
|
||||||
|
Document,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
Heading.configure({ levels: [2, 3] }),
|
||||||
|
Bold,
|
||||||
|
Italic,
|
||||||
|
Underline,
|
||||||
|
BulletList,
|
||||||
|
OrderedList,
|
||||||
|
ListItem,
|
||||||
|
History,
|
||||||
|
CharacterCount.configure({
|
||||||
|
limit: CHARACTER_LIMIT,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: textarea.value || '<p></p>',
|
||||||
|
onCreate({ editor: currentEditor }) {
|
||||||
|
updateToolbarState(toolbar, currentEditor);
|
||||||
|
updateCharacterCounter(counterNode, currentEditor);
|
||||||
|
updateEditorA11yState(mount, textarea, textareaId, currentEditor);
|
||||||
|
},
|
||||||
|
onUpdate({ editor: currentEditor }) {
|
||||||
|
syncState(currentEditor, textarea);
|
||||||
|
updateToolbarState(toolbar, currentEditor);
|
||||||
|
updateCharacterCounter(counterNode, currentEditor);
|
||||||
|
updateEditorA11yState(mount, textarea, textareaId, currentEditor);
|
||||||
|
},
|
||||||
|
onSelectionUpdate({ editor: currentEditor }) {
|
||||||
|
updateToolbarState(toolbar, currentEditor);
|
||||||
|
updateEditorA11yState(mount, textarea, textareaId, currentEditor);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.on('transaction', () => {
|
||||||
|
updateToolbarState(toolbar, editor);
|
||||||
|
updateCharacterCounter(counterNode, editor);
|
||||||
|
updateEditorA11yState(mount, textarea, textareaId, editor);
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.on('focus', () => {
|
||||||
|
updateToolbarState(toolbar, editor);
|
||||||
|
updateCharacterCounter(counterNode, editor);
|
||||||
|
updateEditorA11yState(mount, textarea, textareaId, editor);
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.on('blur', () => {
|
||||||
|
updateToolbarState(toolbar, editor);
|
||||||
|
updateCharacterCounter(counterNode, editor);
|
||||||
|
updateEditorA11yState(mount, textarea, textareaId, editor);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (toolbar) {
|
||||||
|
toolbar.addEventListener('click', (event) => {
|
||||||
|
const target = event.target.closest('button[data-action]');
|
||||||
|
|
||||||
|
if (!target || target.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const action = target.getAttribute('data-action');
|
||||||
|
const chain = editor.chain().focus();
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'paragraph':
|
||||||
|
chain.setParagraph().run();
|
||||||
|
break;
|
||||||
|
case 'h2':
|
||||||
|
chain.toggleHeading({ level: 2 }).run();
|
||||||
|
break;
|
||||||
|
case 'h3':
|
||||||
|
chain.toggleHeading({ level: 3 }).run();
|
||||||
|
break;
|
||||||
|
case 'bold':
|
||||||
|
chain.toggleBold().run();
|
||||||
|
break;
|
||||||
|
case 'italic':
|
||||||
|
chain.toggleItalic().run();
|
||||||
|
break;
|
||||||
|
case 'underline':
|
||||||
|
chain.toggleUnderline().run();
|
||||||
|
break;
|
||||||
|
case 'bulletList':
|
||||||
|
chain.toggleBulletList().run();
|
||||||
|
break;
|
||||||
|
case 'orderedList':
|
||||||
|
chain.toggleOrderedList().run();
|
||||||
|
break;
|
||||||
|
case 'indent':
|
||||||
|
chain.sinkListItem('listItem').run();
|
||||||
|
break;
|
||||||
|
case 'outdent':
|
||||||
|
chain.liftListItem('listItem').run();
|
||||||
|
break;
|
||||||
|
case 'undo':
|
||||||
|
chain.undo().run();
|
||||||
|
break;
|
||||||
|
case 'redo':
|
||||||
|
chain.redo().run();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncState(editor, textarea);
|
||||||
|
updateToolbarState(toolbar, editor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', () => {
|
||||||
|
syncState(editor, textarea);
|
||||||
|
updateEditorA11yState(mount, textarea, textareaId, editor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEditorA11yState(mount, textarea, textareaId, editor);
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M5,14 C7.76005315,14.0033061 9.99669388,16.2399468 10,19 C10,21.7614237 7.76142375,24 5,24 C2.23857625,24 1.77635684e-15,21.7614237 1.77635684e-15,19 C1.77635684e-15,16.2385763 2.23857625,14 5,14 Z M7.5,19.9375 C8.01776695,19.9375 8.4375,19.517767 8.4375,19 C8.4375,18.482233 8.01776695,18.0625 7.5,18.0625 L6.25,18.0625 C6.07741102,18.0625 5.9375,17.922589 5.9375,17.75 L5.9375,16.5 C5.9375,15.982233 5.51776695,15.5625 5,15.5625 C4.48223305,15.5625 4.0625,15.982233 4.0625,16.5 L4.0625,17.75 C4.0625,17.922589 3.92258898,18.0625 3.75,18.0625 L2.5,18.0625 C1.98223305,18.0625 1.5625,18.482233 1.5625,19 C1.5625,19.517767 1.98223305,19.9375 2.5,19.9375 L3.75,19.9375 C3.92258898,19.9375 4.0625,20.077411 4.0625,20.25 L4.0625,21.5 C4.0625,22.017767 4.48223305,22.4375 5,22.4375 C5.51776695,22.4375 5.9375,22.017767 5.9375,21.5 L5.9375,20.25 C5.9375,20.077411 6.07741102,19.9375 6.25,19.9375 L7.5,19.9375 Z M16,19 C16,20.6568542 17.3431458,22 19,22 C20.6568542,22 22,20.6568542 22,19 L22,5 C22,3.34314575 20.6568542,2 19,2 C17.3431458,2 16,3.34314575 16,5 L16,19 Z M14,19 L14,5 C14,2.23857625 16.2385763,0 19,0 C21.7614237,0 24,2.23857625 24,5 L24,19 C24,21.7614237 21.7614237,24 19,24 C16.2385763,24 14,21.7614237 14,19 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M19,14 C21.7600532,14.0033061 23.9966939,16.2399468 24,19 C24,21.7614237 21.7614237,24 19,24 C16.2385763,24 14,21.7614237 14,19 C14,16.2385763 16.2385763,14 19,14 Z M21.5,19.9375 C22.017767,19.9375 22.4375,19.517767 22.4375,19 C22.4375,18.482233 22.017767,18.0625 21.5,18.0625 L20.25,18.0625 C20.077411,18.0625 19.9375,17.922589 19.9375,17.75 L19.9375,16.5 C19.9375,15.982233 19.517767,15.5625 19,15.5625 C18.482233,15.5625 18.0625,15.982233 18.0625,16.5 L18.0625,17.75 C18.0625,17.922589 17.922589,18.0625 17.75,18.0625 L16.5,18.0625 C15.982233,18.0625 15.5625,18.482233 15.5625,19 C15.5625,19.517767 15.982233,19.9375 16.5,19.9375 L17.75,19.9375 C17.922589,19.9375 18.0625,20.077411 18.0625,20.25 L18.0625,21.5 C18.0625,22.017767 18.482233,22.4375 19,22.4375 C19.517767,22.4375 19.9375,22.017767 19.9375,21.5 L19.9375,20.25 C19.9375,20.077411 20.077411,19.9375 20.25,19.9375 L21.5,19.9375 Z M2,19 C2,20.6568542 3.34314575,22 5,22 C6.65685425,22 8,20.6568542 8,19 L8,5 C8,3.34314575 6.65685425,2 5,2 C3.34314575,2 2,3.34314575 2,5 L2,19 Z M-2.7585502e-16,19 L5.81397739e-16,5 C-1.37692243e-16,2.23857625 2.23857625,0 5,0 C7.76142375,0 10,2.23857625 10,5 L10,19 C10,21.7614237 7.76142375,24 5,24 C2.23857625,24 4.43234962e-16,21.7614237 -2.7585502e-16,19 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M19,0 C21.7600532,0.00330611633 23.9966939,2.23994685 24,5 C24,7.76142375 21.7614237,10 19,10 C16.2385763,10 14,7.76142375 14,5 C14,2.23857625 16.2385763,0 19,0 Z M21.5,5.9375 C22.017767,5.9375 22.4375,5.51776695 22.4375,5 C22.4375,4.48223305 22.017767,4.0625 21.5,4.0625 L20.25,4.0625 C20.077411,4.0625 19.9375,3.92258898 19.9375,3.75 L19.9375,2.5 C19.9375,1.98223305 19.517767,1.5625 19,1.5625 C18.482233,1.5625 18.0625,1.98223305 18.0625,2.5 L18.0625,3.75 C18.0625,3.92258898 17.922589,4.0625 17.75,4.0625 L16.5,4.0625 C15.982233,4.0625 15.5625,4.48223305 15.5625,5 C15.5625,5.51776695 15.982233,5.9375 16.5,5.9375 L17.75,5.9375 C17.922589,5.9375 18.0625,6.07741102 18.0625,6.25 L18.0625,7.5 C18.0625,8.01776695 18.482233,8.4375 19,8.4375 C19.517767,8.4375 19.9375,8.01776695 19.9375,7.5 L19.9375,6.25 C19.9375,6.07741102 20.077411,5.9375 20.25,5.9375 L21.5,5.9375 Z M5,16 C3.34314575,16 2,17.3431458 2,19 C2,20.6568542 3.34314575,22 5,22 L19,22 C20.6568542,22 22,20.6568542 22,19 C22,17.3431458 20.6568542,16 19,16 L5,16 Z M5,14 L19,14 C21.7614237,14 24,16.2385763 24,19 C24,21.7614237 21.7614237,24 19,24 L5,24 C2.23857625,24 3.38176876e-16,21.7614237 0,19 C-1.2263553e-15,16.2385763 2.23857625,14 5,14 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M19,14 C21.7600532,14.0033061 23.9966939,16.2399468 24,19 C24,21.7614237 21.7614237,24 19,24 C16.2385763,24 14,21.7614237 14,19 C14,16.2385763 16.2385763,14 19,14 Z M21.5,19.9375 C22.017767,19.9375 22.4375,19.517767 22.4375,19 C22.4375,18.482233 22.017767,18.0625 21.5,18.0625 L20.25,18.0625 C20.077411,18.0625 19.9375,17.922589 19.9375,17.75 L19.9375,16.5 C19.9375,15.982233 19.517767,15.5625 19,15.5625 C18.482233,15.5625 18.0625,15.982233 18.0625,16.5 L18.0625,17.75 C18.0625,17.922589 17.922589,18.0625 17.75,18.0625 L16.5,18.0625 C15.982233,18.0625 15.5625,18.482233 15.5625,19 C15.5625,19.517767 15.982233,19.9375 16.5,19.9375 L17.75,19.9375 C17.922589,19.9375 18.0625,20.077411 18.0625,20.25 L18.0625,21.5 C18.0625,22.017767 18.482233,22.4375 19,22.4375 C19.517767,22.4375 19.9375,22.017767 19.9375,21.5 L19.9375,20.25 C19.9375,20.077411 20.077411,19.9375 20.25,19.9375 L21.5,19.9375 Z M5,2 C3.34314575,2 2,3.34314575 2,5 C2,6.65685425 3.34314575,8 5,8 L19,8 C20.6568542,8 22,6.65685425 22,5 C22,3.34314575 20.6568542,2 19,2 L5,2 Z M5,0 L19,0 C21.7614237,-5.07265313e-16 24,2.23857625 24,5 C24,7.76142375 21.7614237,10 19,10 L5,10 C2.23857625,10 3.38176876e-16,7.76142375 0,5 C-1.2263553e-15,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-bold</title><path d="M17.194,10.962A6.271,6.271,0,0,0,12.844.248H4.3a1.25,1.25,0,0,0,0,2.5H5.313a.25.25,0,0,1,.25.25V21a.25.25,0,0,1-.25.25H4.3a1.25,1.25,0,1,0,0,2.5h9.963a6.742,6.742,0,0,0,2.93-12.786Zm-4.35-8.214a3.762,3.762,0,0,1,0,7.523H8.313a.25.25,0,0,1-.25-.25V3a.25.25,0,0,1,.25-.25Zm1.42,18.5H8.313a.25.25,0,0,1-.25-.25V13.021a.25.25,0,0,1,.25-.25h4.531c.017,0,.033,0,.049,0l.013,0h1.358a4.239,4.239,0,0,1,0,8.477Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 505 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>checklist-alternate</title><path d="M21,0H3A3,3,0,0,0,0,3V21a3,3,0,0,0,3,3H21a3,3,0,0,0,3-3V3A3,3,0,0,0,21,0Zm1,21a1,1,0,0,1-1,1H3a1,1,0,0,1-1-1V3A1,1,0,0,1,3,2H21a1,1,0,0,1,1,1Z"/><path d="M11.249,4.5a1.251,1.251,0,0,0-1.75.25L7.365,7.6l-.482-.481A1.25,1.25,0,0,0,5.116,8.883l1.5,1.5A1.262,1.262,0,0,0,8.5,10.249l3-4A1.25,1.25,0,0,0,11.249,4.5Z"/><path d="M11.249,13.5a1.251,1.251,0,0,0-1.75.25L7.365,16.6l-.482-.481a1.25,1.25,0,1,0-1.767,1.768l1.5,1.5A1.265,1.265,0,0,0,8.5,19.249l3-4A1.25,1.25,0,0,0,11.249,13.5Z"/><path d="M18.5,7.749H14a1.25,1.25,0,0,0,0,2.5h4.5a1.25,1.25,0,0,0,0-2.5Z"/><path d="M18.5,15.749H14a1.25,1.25,0,0,0,0,2.5h4.5a1.25,1.25,0,1,0,0-2.5Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 743 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>angle-brackets</title><path d="M9.147,21.552a1.244,1.244,0,0,1-.895-.378L.84,13.561a2.257,2.257,0,0,1,0-3.125L8.252,2.823a1.25,1.25,0,0,1,1.791,1.744l-6.9,7.083a.5.5,0,0,0,0,.7l6.9,7.082a1.25,1.25,0,0,1-.9,2.122Z"/><path d="M14.854,21.552a1.25,1.25,0,0,1-.9-2.122l6.9-7.083a.5.5,0,0,0,0-.7l-6.9-7.082a1.25,1.25,0,0,1,1.791-1.744l7.411,7.612a2.257,2.257,0,0,1,0,3.125l-7.412,7.614A1.244,1.244,0,0,1,14.854,21.552Zm6.514-9.373h0Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 504 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M2,19 C2,20.6568542 3.34314575,22 5,22 L19,22 C20.6568542,22 22,20.6568542 22,19 L22,5 C22,3.34314575 20.6568542,2 19,2 L5,2 C3.34314575,2 2,3.34314575 2,5 L2,19 Z M-1.16403344e-15,19 L-3.0678068e-16,5 C-6.44957556e-16,2.23857625 2.23857625,0 5,0 L19,0 C21.7614237,0 24,2.23857625 24,5 L24,19 C24,21.7614237 21.7614237,24 19,24 L5,24 C2.23857625,24 9.50500275e-16,21.7614237 -1.16403344e-15,19 Z M12,10 C12.5522847,10 13,10.4477153 13,11 L13,13 C13,13.5522847 12.5522847,14 12,14 C11.4477153,14 11,13.5522847 11,13 L11,11 C11,10.4477153 11.4477153,10 12,10 Z M12,16 C12.5522847,16 13,16.4477153 13,17 L13,20 C13,20.5522847 12.5522847,21 12,21 C11.4477153,21 11,20.5522847 11,20 L11,17 C11,16.4477153 11.4477153,16 12,16 Z M12,3 C12.5522847,3 13,3.44771525 13,4 L13,7 C13,7.55228475 12.5522847,8 12,8 C11.4477153,8 11,7.55228475 11,7 L11,4 C11,3.44771525 11.4477153,3 12,3 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 956 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M12.6414391,21.9312708 C12.9358807,22.5689168 13.3234155,23.1547532 13.7866134,23.6713497 C13.2317936,23.8836754 12.6294813,24 12,24 C9.23857625,24 7,21.7614237 7,19 L7,5 C7,2.23857625 9.23857625,0 12,0 C14.7614237,0 17,2.23857625 17,5 L17,12.2898787 C16.2775651,12.5048858 15.6040072,12.8333806 15,13.2546893 L15,5 C15,3.34314575 13.6568542,2 12,2 C10.3431458,2 9,3.34314575 9,5 L9,19 C9,20.6568542 10.3431458,22 12,22 C12.220157,22 12.4347751,21.9762852 12.6414391,21.9312708 Z M19,14 C21.7600532,14.0033061 23.9966939,16.2399468 24,19 C24,21.7614237 21.7614237,24 19,24 C16.2385763,24 14,21.7614237 14,19 C14,16.2385763 16.2385763,14 19,14 Z M16.5,19.9375 L21.5,19.9375 C22.017767,19.9375 22.4375,19.517767 22.4375,19 C22.4375,18.482233 22.017767,18.0625 21.5,18.0625 L16.5,18.0625 C15.982233,18.0625 15.5625,18.482233 15.5625,19 C15.5625,19.517767 15.982233,19.9375 16.5,19.9375 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 967 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M13.2546893,15 C12.8333806,15.6040072 12.5048858,16.2775651 12.2898787,17 L5,17 C2.23857625,17 3.38176876e-16,14.7614237 0,12 C-1.2263553e-15,9.23857625 2.23857625,7 5,7 L19,7 C21.7614237,7 24,9.23857625 24,12 C24,12.6294813 23.8836754,13.2317936 23.6713497,13.7866134 C23.1547532,13.3234155 22.5689168,12.9358807 21.9312708,12.6414391 C21.9762852,12.4347751 22,12.220157 22,12 C22,10.3431458 20.6568542,9 19,9 L5,9 C3.34314575,9 2,10.3431458 2,12 C2,13.6568542 3.34314575,15 5,15 L13.2546893,15 Z M19,14 C21.7600532,14.0033061 23.9966939,16.2399468 24,19 C24,21.7614237 21.7614237,24 19,24 C16.2385763,24 14,21.7614237 14,19 C14,16.2385763 16.2385763,14 19,14 Z M16.5,19.9375 L21.5,19.9375 C22.017767,19.9375 22.4375,19.517767 22.4375,19 C22.4375,18.482233 22.017767,18.0625 21.5,18.0625 L16.5,18.0625 C15.982233,18.0625 15.5625,18.482233 15.5625,19 C15.5625,19.517767 15.982233,19.9375 16.5,19.9375 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 985 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M19,14 C21.7600532,14.0033061 23.9966939,16.2399468 24,19 C24,21.7614237 21.7614237,24 19,24 C16.2385763,24 14,21.7614237 14,19 C14,16.2385763 16.2385763,14 19,14 Z M16.5,19.9375 L21.5,19.9375 C22.017767,19.9375 22.4375,19.517767 22.4375,19 C22.4375,18.482233 22.017767,18.0625 21.5,18.0625 L16.5,18.0625 C15.982233,18.0625 15.5625,18.482233 15.5625,19 C15.5625,19.517767 15.982233,19.9375 16.5,19.9375 Z M12.2898787,17 L9,17 L9,22 L12.6736312,22 C13.0297295,22.7496048 13.515133,23.4258795 14.1010173,24 L5,24 C2.23857625,24 -1.43817996e-15,21.7614237 -1.77635684e-15,19 L-3.55271368e-15,5 C-3.89089055e-15,2.23857625 2.23857625,5.07265313e-16 5,-1.77635684e-15 L19,-1.77635684e-15 C21.7614237,-2.28362215e-15 24,2.23857625 24,5 L24,7.82313285 C24.0122947,7.88054124 24.0187107,7.93964623 24.0187107,8 C24.0187107,8.06035377 24.0122947,8.11945876 24,8.17686715 L24,14.1010173 C23.4258795,13.515133 22.7496048,13.0297295 22,12.6736312 L22,9 L17,9 L17,12.2898787 C16.2775651,12.5048858 15.6040072,12.8333806 15,13.2546893 L15,9 L9,9 L9,15 L13.2546893,15 C12.8333806,15.6040072 12.5048858,16.2775651 12.2898787,17 Z M17,7 L22,7 L22,5 C22,3.34314575 20.6568542,2 19,2 L17,2 L17,7 Z M15,7 L15,2 L9,2 L9,7 L15,7 Z M7,2 L5,2 C3.34314575,2 2,3.34314575 2,5 L2,7 L7,7 L7,2 Z M2,9 L2,15 L7,15 L7,9 L2,9 Z M2,17 L2,19 C2,20.6568542 3.34314575,22 5,22 L7,22 L7,17 L2,17 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path d="M11.999,0.5 C5.649,0.5 0.5,5.648 0.5,12 C0.5,17.082 3.794,21.392 8.365,22.914 C8.939,23.017 9.121,22.678 9.121,22.373 C9.121,22.099 9.127,21.336 9.121,20.376 C5.923,21.07 5.26,18.861 5.26,18.861 C4.737,17.532 3.985,17.179 3.985,17.179 C2.94,16.465 4.062,16.48 4.062,16.48 C5.215,16.56 5.824,17.664 5.824,17.664 C6.85,19.422 8.515,18.914 9.17,18.62 C9.276,17.878 9.572,17.369 9.901,17.084 C7.347,16.792 4.663,15.807 4.663,11.398 C4.663,10.143 5.111,9.117 5.847,8.312 C5.729,8.023 5.333,6.852 5.959,5.269 C5.959,5.269 6.926,4.96 9.121,6.449 C10.039,6.193 11.023,6.066 12.001,6.061 C12.977,6.066 13.961,6.193 14.881,6.449 C17.076,4.961 18.04,5.269 18.04,5.269 C18.667,6.852 18.272,8.023 18.154,8.312 C18.89,9.117 19.337,10.143 19.337,11.398 C19.337,15.818 16.648,16.789 14.086,17.072 C14.498,17.429 14.873,18.119 14.873,19.192 C14.873,20.63 14.873,21.998 14.873,22.376 C14.873,22.684 15.059,23.023 15.643,22.912 C20.209,21.389 23.5,17.08 23.5,12 C23.5,5.648 18.352,0.5 11.999,0.5 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 6V18M10 6V18M4 12H10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M14 8.5C14 7.12 15.12 6 16.5 6H17.5C18.88 6 20 7.12 20 8.5C20 9.39 19.53 10.22 18.76 10.67L14 13.5H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 405 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 6V18M10 6V18M4 12H10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M16.5 6H17.5C18.88 6 20 7.12 20 8.5C20 9.88 18.88 11 17.5 11C18.88 11 20 12.12 20 13.5C20 14.88 18.88 16 17.5 16H16.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 421 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>horizontal-rule</title>
|
||||||
|
<path d="M5,13 C4.44771525,13 4,12.5522847 4,12 C4,11.4477153 4.44771525,11 5,11 L19,11 C19.5522847,11 20,11.4477153 20,12 C20,12.5522847 19.5522847,13 19,13 L5,13 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 269 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paginate-filter-picture-alternate</title><circle cx="9.75" cy="6.247" r="2.25"/><path d="M16.916,8.71A1.027,1.027,0,0,0,16,8.158a1.007,1.007,0,0,0-.892.586L13.55,12.178a.249.249,0,0,1-.422.053l-.82-1.024a1,1,0,0,0-.813-.376,1.007,1.007,0,0,0-.787.426L7.59,15.71A.5.5,0,0,0,8,16.5H20a.5.5,0,0,0,.425-.237.5.5,0,0,0,.022-.486Z"/><path d="M22,0H5.5a2,2,0,0,0-2,2V18.5a2,2,0,0,0,2,2H22a2,2,0,0,0,2-2V2A2,2,0,0,0,22,0Zm-.145,18.354a.5.5,0,0,1-.354.146H6a.5.5,0,0,1-.5-.5V2.5A.5.5,0,0,1,6,2H21.5a.5.5,0,0,1,.5.5V18A.5.5,0,0,1,21.855,18.351Z"/><path d="M19.5,22H2.5a.5.5,0,0,1-.5-.5V4.5a1,1,0,0,0-2,0V22a2,2,0,0,0,2,2H19.5a1,1,0,0,0,0-2Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 707 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 7H20M4 12H14M4 17H20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M10 9L14 12L10 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-italic</title><path d="M22.5.248H14.863a1.25,1.25,0,0,0,0,2.5h1.086a.25.25,0,0,1,.211.384L4.78,21.017a.5.5,0,0,1-.422.231H1.5a1.25,1.25,0,0,0,0,2.5H9.137a1.25,1.25,0,0,0,0-2.5H8.051a.25.25,0,0,1-.211-.384L19.22,2.98a.5.5,0,0,1,.422-.232H22.5a1.25,1.25,0,0,0,0-2.5Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 346 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>hyperlink-2</title><path d="M12.406,14.905a1,1,0,0,0-.543,1.307,1,1,0,0,1-.217,1.09L8.818,20.131a2,2,0,0,1-2.828,0L3.868,18.01a2,2,0,0,1,0-2.829L6.7,12.353a1.013,1.013,0,0,1,1.091-.217,1,1,0,0,0,.763-1.849,3.034,3.034,0,0,0-3.268.652L2.454,13.767a4.006,4.006,0,0,0,0,5.657l2.122,2.121a4,4,0,0,0,5.656,0l2.829-2.828a3.008,3.008,0,0,0,.651-3.27A1,1,0,0,0,12.406,14.905Z"/><path d="M7.757,16.241a1.011,1.011,0,0,0,1.414,0L16.95,8.463a1,1,0,0,0-1.414-1.414L7.757,14.827A1,1,0,0,0,7.757,16.241Z"/><path d="M21.546,4.574,19.425,2.453a4.006,4.006,0,0,0-5.657,0L10.939,5.281a3.006,3.006,0,0,0-.651,3.269,1,1,0,1,0,1.849-.764A1,1,0,0,1,12.354,6.7l2.828-2.828a2,2,0,0,1,2.829,0l2.121,2.121a2,2,0,0,1,0,2.829L17.3,11.645a1.015,1.015,0,0,1-1.091.217,1,1,0,0,0-.765,1.849,3.026,3.026,0,0,0,3.27-.651l2.828-2.828A4.007,4.007,0,0,0,21.546,4.574Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 907 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>read-email-at-alternate</title><path d="M12,.5A11.634,11.634,0,0,0,.262,12,11.634,11.634,0,0,0,12,23.5a11.836,11.836,0,0,0,6.624-2,1.25,1.25,0,1,0-1.393-2.076A9.34,9.34,0,0,1,12,21a9.132,9.132,0,0,1-9.238-9A9.132,9.132,0,0,1,12,3a9.132,9.132,0,0,1,9.238,9v.891a1.943,1.943,0,0,1-3.884,0V12A5.355,5.355,0,1,0,12,17.261a5.376,5.376,0,0,0,3.861-1.634,4.438,4.438,0,0,0,7.877-2.736V12A11.634,11.634,0,0,0,12,.5Zm0,14.261A2.763,2.763,0,1,1,14.854,12,2.812,2.812,0,0,1,12,14.761Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 549 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>list-numbers</title><path d="M7.75,4.5h15a1,1,0,0,0,0-2h-15a1,1,0,0,0,0,2Z"/><path d="M22.75,11h-15a1,1,0,1,0,0,2h15a1,1,0,0,0,0-2Z"/><path d="M22.75,19.5h-15a1,1,0,0,0,0,2h15a1,1,0,0,0,0-2Z"/><path d="M2.212,17.248A2,2,0,0,0,.279,18.732a.75.75,0,1,0,1.45.386.5.5,0,1,1,.483.63.75.75,0,1,0,0,1.5.5.5,0,1,1-.482.635.75.75,0,1,0-1.445.4,2,2,0,1,0,3.589-1.648.251.251,0,0,1,0-.278,2,2,0,0,0-1.662-3.111Z"/><path d="M4.25,10.748a2,2,0,0,0-4,0,.75.75,0,0,0,1.5,0,.5.5,0,0,1,1,0,1.031,1.031,0,0,1-.227.645L.414,14.029A.75.75,0,0,0,1,15.248H3.5a.75.75,0,0,0,0-1.5H3.081a.249.249,0,0,1-.195-.406L3.7,12.33A2.544,2.544,0,0,0,4.25,10.748Z"/><path d="M4,5.248H3.75A.25.25,0,0,1,3.5,5V1.623A1.377,1.377,0,0,0,2.125.248H1.5a.75.75,0,0,0,0,1.5h.25A.25.25,0,0,1,2,2V5a.25.25,0,0,1-.25.25H1.5a.75.75,0,0,0,0,1.5H4a.75.75,0,0,0,0-1.5Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 894 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 7H20M10 12H20M4 17H20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M14 9L10 12L14 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 322 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paragraph</title><path d="M22.5.248H7.228a6.977,6.977,0,1,0,0,13.954H9.546a.25.25,0,0,1,.25.25V22.5a1.25,1.25,0,0,0,2.5,0V3a.25.25,0,0,1,.25-.25h3.682a.25.25,0,0,1,.25.25V22.5a1.25,1.25,0,0,0,2.5,0V3a.249.249,0,0,1,.25-.25H22.5a1.25,1.25,0,0,0,0-2.5ZM9.8,11.452a.25.25,0,0,1-.25.25H7.228a4.477,4.477,0,1,1,0-8.954H9.546A.25.25,0,0,1,9.8,3Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 416 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>close-quote</title><path d="M18.559,3.932a4.942,4.942,0,1,0,0,9.883,4.609,4.609,0,0,0,1.115-.141.25.25,0,0,1,.276.368,6.83,6.83,0,0,1-5.878,3.523,1.25,1.25,0,0,0,0,2.5,9.71,9.71,0,0,0,9.428-9.95V8.873A4.947,4.947,0,0,0,18.559,3.932Z"/><path d="M6.236,3.932a4.942,4.942,0,0,0,0,9.883,4.6,4.6,0,0,0,1.115-.141.25.25,0,0,1,.277.368A6.83,6.83,0,0,1,1.75,17.565a1.25,1.25,0,0,0,0,2.5,9.711,9.711,0,0,0,9.428-9.95V8.873A4.947,4.947,0,0,0,6.236,3.932Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 521 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>redo</title><path d="M22.608.161a.5.5,0,0,0-.545.108L19.472,2.86a.25.25,0,0,1-.292.045A12.537,12.537,0,0,0,6.214,3.77,12.259,12.259,0,0,0,6.1,23.632a1.25,1.25,0,0,0,1.476-2.018A9.759,9.759,0,0,1,7.667,5.805a10,10,0,0,1,9.466-1.1.25.25,0,0,1,.084.409l-1.85,1.85a.5.5,0,0,0,.354.853h6.7a.5.5,0,0,0,.5-.5V.623A.5.5,0,0,0,22.608.161Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 406 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>delete-2-alternate</title><path d="M20.485,3.511A12.01,12.01,0,1,0,24,12,12.009,12.009,0,0,0,20.485,3.511Zm-1.767,15.21A9.51,9.51,0,1,1,21.5,12,9.508,9.508,0,0,1,18.718,18.721Z"/><path d="M16.987,7.01a1.275,1.275,0,0,0-1.8,0l-3.177,3.177L8.829,7.01A1.277,1.277,0,0,0,7.024,8.816L10.2,11.993,7.024,15.171a1.277,1.277,0,0,0,1.805,1.806L12.005,13.8l3.177,3.178a1.277,1.277,0,0,0,1.8-1.806l-3.176-3.178,3.176-3.177A1.278,1.278,0,0,0,16.987,7.01Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 518 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-strike-through</title><path d="M23.75,12.952A1.25,1.25,0,0,0,22.5,11.7H13.564a.492.492,0,0,1-.282-.09c-.722-.513-1.482-.981-2.218-1.432-2.8-1.715-4.5-2.9-4.5-4.863,0-2.235,2.207-2.569,3.523-2.569a4.54,4.54,0,0,1,3.081.764A2.662,2.662,0,0,1,13.615,5.5l0,.3a1.25,1.25,0,1,0,2.5,0l0-.268A4.887,4.887,0,0,0,14.95,1.755C13.949.741,12.359.248,10.091.248c-3.658,0-6.023,1.989-6.023,5.069,0,2.773,1.892,4.512,4,5.927a.25.25,0,0,1-.139.458H1.5a1.25,1.25,0,0,0,0,2.5H12.477a.251.251,0,0,1,.159.058,4.339,4.339,0,0,1,1.932,3.466c0,3.268-3.426,3.522-4.477,3.522-1.814,0-3.139-.405-3.834-1.173a3.394,3.394,0,0,1-.65-2.7,1.25,1.25,0,0,0-2.488-.246A5.76,5.76,0,0,0,4.4,21.753c1.2,1.324,3.114,2,5.688,2,4.174,0,6.977-2.42,6.977-6.022a6.059,6.059,0,0,0-.849-3.147.25.25,0,0,1,.216-.377H22.5A1.25,1.25,0,0,0,23.75,12.952Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 885 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path fill-rule="evenodd" d="M17,17 L17,22 L19,22 C20.6568542,22 22,20.6568542 22,19 L22,17 L17,17 Z M15,17 L9,17 L9,22 L15,22 L15,17 Z M17,15 L22,15 L22,9 L17,9 L17,15 Z M15,15 L15,9 L9,9 L9,15 L15,15 Z M17,7 L22,7 L22,5 C22,3.34314575 20.6568542,2 19,2 L17,2 L17,7 Z M15,7 L15,2 L9,2 L9,7 L15,7 Z M24,16.1768671 L24,19 C24,21.7614237 21.7614237,24 19,24 L5,24 C2.23857625,24 2.11453371e-15,21.7614237 1.77635684e-15,19 L0,5 C-3.38176876e-16,2.23857625 2.23857625,2.28362215e-15 5,0 L19,0 C21.7614237,-5.07265313e-16 24,2.23857625 24,5 L24,7.82313285 C24.0122947,7.88054124 24.0187107,7.93964623 24.0187107,8 C24.0187107,8.06035377 24.0122947,8.11945876 24,8.17686715 L24,15.8231329 C24.0122947,15.8805412 24.0187107,15.9396462 24.0187107,16 C24.0187107,16.0603538 24.0122947,16.1194588 24,16.1768671 Z M7,2 L5,2 C3.34314575,2 2,3.34314575 2,5 L2,7 L7,7 L7,2 Z M2,9 L2,15 L7,15 L7,9 L2,9 Z M2,17 L2,19 C2,20.6568542 3.34314575,22 5,22 L7,22 L7,17 L2,17 Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>list-bullets</title><circle cx="2.5" cy="3.998" r="2.5"/><path d="M8.5,5H23a1,1,0,0,0,0-2H8.5a1,1,0,0,0,0,2Z"/><circle cx="2.5" cy="11.998" r="2.5"/><path d="M23,11H8.5a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><circle cx="2.5" cy="19.998" r="2.5"/><path d="M23,19H8.5a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 369 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-underline</title><path d="M22.5,21.248H1.5a1.25,1.25,0,0,0,0,2.5h21a1.25,1.25,0,0,0,0-2.5Z"/><path d="M1.978,2.748H3.341a.25.25,0,0,1,.25.25v8.523a8.409,8.409,0,0,0,16.818,0V3a.25.25,0,0,1,.25-.25h1.363a1.25,1.25,0,0,0,0-2.5H16.3a1.25,1.25,0,0,0,0,2.5h1.363a.25.25,0,0,1,.25.25v8.523a5.909,5.909,0,0,1-11.818,0V3a.25.25,0,0,1,.25-.25H7.7a1.25,1.25,0,1,0,0-2.5H1.978a1.25,1.25,0,0,0,0,2.5Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 470 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>undo</title><path d="M17.786,3.77A12.542,12.542,0,0,0,4.821,2.905a.249.249,0,0,1-.292-.045L1.937.269A.507.507,0,0,0,1.392.16a.5.5,0,0,0-.308.462v6.7a.5.5,0,0,0,.5.5h6.7a.5.5,0,0,0,.354-.854L6.783,5.115a.253.253,0,0,1-.068-.228.249.249,0,0,1,.152-.181,10,10,0,0,1,9.466,1.1,9.759,9.759,0,0,1,.094,15.809A1.25,1.25,0,0,0,17.9,23.631a12.122,12.122,0,0,0,5.013-9.961A12.125,12.125,0,0,0,17.786,3.77Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 472 B |
@@ -2,21 +2,21 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Contao\Manager;
|
namespace Mummert\EventManagerBundle\Contao\Manager;
|
||||||
|
|
||||||
use Contao\CalendarBundle\ContaoCalendarBundle;
|
use Contao\CalendarBundle\ContaoCalendarBundle;
|
||||||
use Contao\CoreBundle\ContaoCoreBundle;
|
use Contao\CoreBundle\ContaoCoreBundle;
|
||||||
use Contao\ManagerPlugin\Bundle\BundlePluginInterface;
|
use Contao\ManagerPlugin\Bundle\BundlePluginInterface;
|
||||||
use Contao\ManagerPlugin\Bundle\Config\BundleConfig;
|
use Contao\ManagerPlugin\Bundle\Config\BundleConfig;
|
||||||
use Contao\ManagerPlugin\Bundle\Parser\ParserInterface;
|
use Contao\ManagerPlugin\Bundle\Parser\ParserInterface;
|
||||||
use MummertMedia\EventManagerBundle\MummertMediaEventManagerBundle;
|
use Mummert\EventManagerBundle\EventManagerBundle;
|
||||||
|
|
||||||
class Plugin implements BundlePluginInterface
|
class Plugin implements BundlePluginInterface
|
||||||
{
|
{
|
||||||
public function getBundles(ParserInterface $parser): iterable
|
public function getBundles(ParserInterface $parser): iterable
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
BundleConfig::create(MummertMediaEventManagerBundle::class)
|
BundleConfig::create(EventManagerBundle::class)
|
||||||
->setLoadAfter([ContaoCoreBundle::class, ContaoCalendarBundle::class]),
|
->setLoadAfter([ContaoCoreBundle::class, ContaoCalendarBundle::class]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
namespace Mummert\EventManagerBundle\Controller\Frontend;
|
||||||
|
|
||||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||||
@@ -13,8 +13,8 @@ use Contao\FrontendUser;
|
|||||||
use Contao\ModuleModel;
|
use Contao\ModuleModel;
|
||||||
use Contao\PageModel;
|
use Contao\PageModel;
|
||||||
use Contao\StringUtil;
|
use Contao\StringUtil;
|
||||||
use MummertMedia\EventManagerBundle\Form\EventType;
|
use Mummert\EventManagerBundle\Form\EventType;
|
||||||
use MummertMedia\EventManagerBundle\Service\EventRepository;
|
use Mummert\EventManagerBundle\Service\EventRepository;
|
||||||
use Symfony\Component\Form\FormError;
|
use Symfony\Component\Form\FormError;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
@@ -103,8 +103,9 @@ class EventEditController extends AbstractFrontendModuleController
|
|||||||
'endTime' => $this->resolveFormEndTime($event),
|
'endTime' => $this->resolveFormEndTime($event),
|
||||||
'location_id' => (int) ($event['location_id'] ?? 0),
|
'location_id' => (int) ($event['location_id'] ?? 0),
|
||||||
'tags' => $currentTagIds,
|
'tags' => $currentTagIds,
|
||||||
'teaser' => (string) ($event['teaser'] ?? ''),
|
'teaser' => '' !== trim((string) ($event['teaser'] ?? ''))
|
||||||
'description' => (string) ($event['description'] ?? ''),
|
? (string) ($event['teaser'] ?? '')
|
||||||
|
: (string) ($event['description'] ?? ''),
|
||||||
'url' => (string) ($event['url'] ?? ''),
|
'url' => (string) ($event['url'] ?? ''),
|
||||||
'photographer' => (string) ($event['photographer'] ?? ''),
|
'photographer' => (string) ($event['photographer'] ?? ''),
|
||||||
'addImage' => '1' === (string) ($event['addImage'] ?? ''),
|
'addImage' => '1' === (string) ($event['addImage'] ?? ''),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
namespace Mummert\EventManagerBundle\Controller\Frontend;
|
||||||
|
|
||||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
namespace Mummert\EventManagerBundle\Controller\Frontend;
|
||||||
|
|
||||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||||
use Contao\CoreBundle\Twig\FragmentTemplate;
|
use Contao\CoreBundle\Twig\FragmentTemplate;
|
||||||
use Contao\ModuleModel;
|
use Contao\ModuleModel;
|
||||||
|
use Contao\PageModel;
|
||||||
use Contao\StringUtil;
|
use Contao\StringUtil;
|
||||||
use MummertMedia\EventManagerBundle\Service\MapModuleDataProvider;
|
use Mummert\EventManagerBundle\Service\MapModuleDataProvider;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
@@ -44,9 +45,20 @@ class EventMapController extends AbstractFrontendModuleController
|
|||||||
array_map('intval', StringUtil::deserialize($model->organizationTypeTags ?? null, true)),
|
array_map('intval', StringUtil::deserialize($model->organizationTypeTags ?? null, true)),
|
||||||
static fn (int $tagId): bool => $tagId > 0,
|
static fn (int $tagId): bool => $tagId > 0,
|
||||||
)));
|
)));
|
||||||
|
$availableOrganizationTags = $this->mapModuleDataProvider->getOrganizationTags();
|
||||||
|
$availableOrganizationTagIds = array_map(
|
||||||
|
static fn (array $tag): int => (int) ($tag['id'] ?? 0),
|
||||||
|
$availableOrganizationTags,
|
||||||
|
);
|
||||||
|
$selectedOrganizationTagIds = [] === $selectedOrganizationTagIds
|
||||||
|
? []
|
||||||
|
: array_values(array_intersect($selectedOrganizationTagIds, $availableOrganizationTagIds));
|
||||||
|
$organizationListPageId = (int) ($model->mapOrganizationListPage ?? 0);
|
||||||
|
$organizationListBaseUrl = $this->buildOrganizationListBaseUrl($organizationListPageId, $request);
|
||||||
$initialDisplay = (string) ($model->mapInitialDisplay ?? self::DEFAULT_INITIAL_DISPLAY);
|
$initialDisplay = (string) ($model->mapInitialDisplay ?? self::DEFAULT_INITIAL_DISPLAY);
|
||||||
$initialOrganizationTagId = (int) ($model->mapInitialOrganizationTagId ?? 0);
|
$initialOrganizationTagId = (int) ($model->mapInitialOrganizationTagId ?? 0);
|
||||||
$centerMode = (string) ($model->mapCenterMode ?? self::DEFAULT_CENTER_MODE);
|
$centerMode = (string) ($model->mapCenterMode ?? self::DEFAULT_CENTER_MODE);
|
||||||
|
$mapPitch = $this->normalizePitch($model->mapPitch ?? 0);
|
||||||
$eventColor = $this->normalizeHexColor((string) ($model->mapEventColor ?? self::DEFAULT_EVENT_COLOR));
|
$eventColor = $this->normalizeHexColor((string) ($model->mapEventColor ?? self::DEFAULT_EVENT_COLOR));
|
||||||
$organizationColor = $this->normalizeHexColor((string) ($model->mapOrganizationColor ?? $eventColor), $eventColor);
|
$organizationColor = $this->normalizeHexColor((string) ($model->mapOrganizationColor ?? $eventColor), $eventColor);
|
||||||
|
|
||||||
@@ -77,8 +89,9 @@ class EventMapController extends AbstractFrontendModuleController
|
|||||||
$template->set('mapCenterLat', trim((string) ($model->mapCenterLat ?? '')));
|
$template->set('mapCenterLat', trim((string) ($model->mapCenterLat ?? '')));
|
||||||
$template->set('mapCenterLng', trim((string) ($model->mapCenterLng ?? '')));
|
$template->set('mapCenterLng', trim((string) ($model->mapCenterLng ?? '')));
|
||||||
$template->set('mapCenterZoom', (int) ($model->mapCenterZoom ?? 12));
|
$template->set('mapCenterZoom', (int) ($model->mapCenterZoom ?? 12));
|
||||||
|
$template->set('mapPitch', $mapPitch);
|
||||||
$template->set('mapItemsJson', json_encode(
|
$template->set('mapItemsJson', json_encode(
|
||||||
$this->mapModuleDataProvider->getMapItems($showOrganizations, $showEvents, $showExternalOrganizations, $selectedOrganizationTagIds),
|
$this->mapModuleDataProvider->getMapItems($showOrganizations, $showEvents, $showExternalOrganizations, $selectedOrganizationTagIds, $organizationListBaseUrl),
|
||||||
\JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_APOS | \JSON_HEX_QUOT | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR,
|
\JSON_HEX_TAG | \JSON_HEX_AMP | \JSON_HEX_APOS | \JSON_HEX_QUOT | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR,
|
||||||
));
|
));
|
||||||
$template->set('mapOrganizationTags', $this->mapModuleDataProvider->getOrganizationTags($selectedOrganizationTagIds));
|
$template->set('mapOrganizationTags', $this->mapModuleDataProvider->getOrganizationTags($selectedOrganizationTagIds));
|
||||||
@@ -118,4 +131,48 @@ class EventMapController extends AbstractFrontendModuleController
|
|||||||
|
|
||||||
return $fallback;
|
return $fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function normalizePitch(mixed $value): int
|
||||||
|
{
|
||||||
|
if (!is_scalar($value)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsedPitch = (int) round((float) str_replace(',', '.', (string) $value));
|
||||||
|
|
||||||
|
if ($parsedPitch < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($parsedPitch > 85) {
|
||||||
|
return 85;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parsedPitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildOrganizationListBaseUrl(int $pageId, Request $request): string
|
||||||
|
{
|
||||||
|
if ($pageId <= 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageModel = PageModel::findById($pageId);
|
||||||
|
|
||||||
|
if (null === $pageModel) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$frontendUrl = trim((string) $pageModel->getFrontendUrl());
|
||||||
|
|
||||||
|
if ('' === $frontendUrl) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('#^https?://#i', $frontendUrl)) {
|
||||||
|
return $frontendUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtrim($request->getSchemeAndHttpHost(), '/').'/'.ltrim($frontendUrl, '/');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
namespace Mummert\EventManagerBundle\Controller\Frontend;
|
||||||
|
|
||||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||||
@@ -11,7 +11,7 @@ use Contao\FrontendUser;
|
|||||||
use Contao\ModuleModel;
|
use Contao\ModuleModel;
|
||||||
use Contao\PageModel;
|
use Contao\PageModel;
|
||||||
use Contao\StringUtil;
|
use Contao\StringUtil;
|
||||||
use MummertMedia\EventManagerBundle\Service\EventRepository;
|
use Mummert\EventManagerBundle\Service\EventRepository;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
namespace Mummert\EventManagerBundle\Controller\Frontend;
|
||||||
|
|
||||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||||
@@ -11,7 +11,7 @@ use Contao\FrontendUser;
|
|||||||
use Contao\ModuleModel;
|
use Contao\ModuleModel;
|
||||||
use Contao\PageModel;
|
use Contao\PageModel;
|
||||||
use Contao\StringUtil;
|
use Contao\StringUtil;
|
||||||
use MummertMedia\EventManagerBundle\Service\OrganizationRepository;
|
use Mummert\EventManagerBundle\Service\OrganizationRepository;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Controller\Frontend;
|
namespace Mummert\EventManagerBundle\Controller\Frontend;
|
||||||
|
|
||||||
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
|
||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||||
@@ -13,8 +13,8 @@ use Contao\FrontendUser;
|
|||||||
use Contao\ModuleModel;
|
use Contao\ModuleModel;
|
||||||
use Contao\PageModel;
|
use Contao\PageModel;
|
||||||
use Contao\StringUtil;
|
use Contao\StringUtil;
|
||||||
use MummertMedia\EventManagerBundle\Form\OrganizationType;
|
use Mummert\EventManagerBundle\Form\OrganizationType;
|
||||||
use MummertMedia\EventManagerBundle\Service\OrganizationRepository;
|
use Mummert\EventManagerBundle\Service\OrganizationRepository;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\DependencyInjection;
|
namespace Mummert\EventManagerBundle\DependencyInjection;
|
||||||
|
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||||
|
|
||||||
class MummertMediaEventManagerExtension extends Extension
|
class EventManagerExtension extends Extension
|
||||||
{
|
{
|
||||||
public function load(array $configs, ContainerBuilder $container): void
|
public function load(array $configs, ContainerBuilder $container): void
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\EventListener\DataContainer;
|
namespace Mummert\EventManagerBundle\EventListener\DataContainer;
|
||||||
|
|
||||||
use Contao\DataContainer;
|
use Contao\DataContainer;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\EventListener;
|
namespace Mummert\EventManagerBundle\EventListener;
|
||||||
|
|
||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
||||||
use Contao\StringUtil;
|
use Contao\StringUtil;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\EventListener;
|
namespace Mummert\EventManagerBundle\EventListener;
|
||||||
|
|
||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
||||||
use Contao\Module;
|
use Contao\Module;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\EventListener;
|
namespace Mummert\EventManagerBundle\EventListener;
|
||||||
|
|
||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
||||||
use Contao\StringUtil;
|
use Contao\StringUtil;
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle;
|
namespace Mummert\EventManagerBundle;
|
||||||
|
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
|
|
||||||
class MummertMediaEventManagerBundle extends Bundle
|
class EventManagerBundle extends Bundle
|
||||||
{
|
{
|
||||||
public function getPath(): string
|
public function getPath(): string
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Form;
|
namespace Mummert\EventManagerBundle\Form;
|
||||||
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
@@ -119,17 +119,19 @@ class EventType extends AbstractType
|
|||||||
],
|
],
|
||||||
])
|
])
|
||||||
->add('tags', ChoiceType::class, [
|
->add('tags', ChoiceType::class, [
|
||||||
'label' => 'Typen',
|
'label' => 'Kategorien',
|
||||||
'choices' => $options['tag_choices'],
|
'choices' => $options['tag_choices'],
|
||||||
|
'expanded' => true,
|
||||||
'multiple' => true,
|
'multiple' => true,
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'attr' => [
|
'choice_attr' => static function (): array {
|
||||||
'class' => 'js-event-tags-choice',
|
return [
|
||||||
'data-placeholder' => 'Typen suchen …',
|
'role' => 'switch',
|
||||||
],
|
'class' => 'ns-tag-switch-input',
|
||||||
|
];
|
||||||
|
},
|
||||||
])
|
])
|
||||||
->add('teaser', TextareaType::class, ['label' => 'Teaser', 'required' => false])
|
->add('teaser', TextareaType::class, ['label' => 'Beschreibung', 'required' => false])
|
||||||
->add('description', TextareaType::class, ['label' => 'Beschreibung', 'required' => false])
|
|
||||||
->add('url', UrlType::class, [
|
->add('url', UrlType::class, [
|
||||||
'label' => 'Link (extern)',
|
'label' => 'Link (extern)',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Form;
|
namespace Mummert\EventManagerBundle\Form;
|
||||||
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
@@ -20,23 +20,25 @@ 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])
|
||||||
->add('description', TextareaType::class, ['label' => 'Beschreibung', 'required' => false])
|
->add('description', TextareaType::class, ['label' => 'Beschreibung', 'required' => false])
|
||||||
->add('tags', ChoiceType::class, [
|
->add('tags', ChoiceType::class, [
|
||||||
'label' => 'Typen',
|
'label' => 'Kategorien',
|
||||||
'choices' => $options['tag_choices'],
|
'choices' => $options['tag_choices'],
|
||||||
|
'expanded' => true,
|
||||||
'multiple' => true,
|
'multiple' => true,
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'attr' => [
|
'choice_attr' => static function (): array {
|
||||||
'class' => 'js-organization-tags-choice',
|
return [
|
||||||
'data-placeholder' => 'Typ auswählen …',
|
'role' => 'switch',
|
||||||
],
|
'class' => 'ns-tag-switch-input',
|
||||||
|
];
|
||||||
|
},
|
||||||
])
|
])
|
||||||
->add('logoUpload', FileType::class, [
|
->add('logoUpload', FileType::class, [
|
||||||
'label' => 'Logo hochladen',
|
'label' => 'Logo hochladen',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Model;
|
namespace Mummert\EventManagerBundle\Model;
|
||||||
|
|
||||||
use Contao\Model;
|
use Contao\Model;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Model;
|
namespace Mummert\EventManagerBundle\Model;
|
||||||
|
|
||||||
use Contao\Model;
|
use Contao\Model;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Service;
|
namespace Mummert\EventManagerBundle\Service;
|
||||||
|
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use Doctrine\DBAL\ParameterType;
|
use Doctrine\DBAL\ParameterType;
|
||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Service;
|
namespace Mummert\EventManagerBundle\Service;
|
||||||
|
|
||||||
use Contao\Config;
|
use Contao\CalendarEventsModel;
|
||||||
use Contao\Date;
|
use Contao\CoreBundle\Framework\ContaoFramework;
|
||||||
|
use Contao\CoreBundle\Routing\ContentUrlGenerator;
|
||||||
use Doctrine\DBAL\ArrayParameterType;
|
use Doctrine\DBAL\ArrayParameterType;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use Doctrine\DBAL\ParameterType;
|
use Doctrine\DBAL\ParameterType;
|
||||||
use Doctrine\DBAL\Query\QueryBuilder;
|
use Doctrine\DBAL\Query\QueryBuilder;
|
||||||
|
use Symfony\Component\Routing\Exception\ExceptionInterface;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
|
||||||
class MapModuleDataProvider
|
class MapModuleDataProvider
|
||||||
{
|
{
|
||||||
@@ -20,13 +23,15 @@ class MapModuleDataProvider
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Connection $connection,
|
private readonly Connection $connection,
|
||||||
|
private readonly ContaoFramework $framework,
|
||||||
|
private readonly ContentUrlGenerator $urlGenerator,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<array{type:string,markerType:string,id:int,title:string,latitude:float,longitude:float,extra:array<string,mixed>}>
|
* @return list<array{type:string,markerType:string,id:int,title:string,latitude:float,longitude:float,extra:array<string,mixed>}>
|
||||||
*/
|
*/
|
||||||
public function getMapItems(bool $includeOrganizations = true, bool $includeEvents = true, bool $includeExternalOrganizations = false, array $selectedOrganizationTagIds = []): array
|
public function getMapItems(bool $includeOrganizations = true, bool $includeEvents = true, bool $includeExternalOrganizations = false, array $selectedOrganizationTagIds = [], string $organizationListBaseUrl = ''): array
|
||||||
{
|
{
|
||||||
if (!$includeOrganizations && !$includeEvents) {
|
if (!$includeOrganizations && !$includeEvents) {
|
||||||
return [];
|
return [];
|
||||||
@@ -97,37 +102,13 @@ class MapModuleDataProvider
|
|||||||
'extra' => [
|
'extra' => [
|
||||||
'organizationTagIds' => $tagData['ids'],
|
'organizationTagIds' => $tagData['ids'],
|
||||||
'organizationTagLabels' => $tagData['labels'],
|
'organizationTagLabels' => $tagData['labels'],
|
||||||
|
'detailUrl' => $this->buildOrganizationDetailUrl($organizationListBaseUrl, $id),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($includeEvents) {
|
if ($includeEvents) {
|
||||||
foreach ($this->fetchLocationRows($locationTable, $locationGeoColumns) as $row) {
|
|
||||||
$id = (int) ($row['id'] ?? 0);
|
|
||||||
|
|
||||||
if ($id <= 0 || isset($seen['location'][$id])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$coords = $this->extractCoordinates($row['latitude'] ?? null, $row['longitude'] ?? null);
|
|
||||||
|
|
||||||
if (null === $coords) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$seen['location'][$id] = true;
|
|
||||||
$items[] = [
|
|
||||||
'type' => 'location',
|
|
||||||
'markerType' => 'location',
|
|
||||||
'id' => $id,
|
|
||||||
'title' => trim((string) ($row['title'] ?? '')),
|
|
||||||
'latitude' => $coords['latitude'],
|
|
||||||
'longitude' => $coords['longitude'],
|
|
||||||
'extra' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->fetchEventRows($locationTable, $locationGeoColumns) as $row) {
|
foreach ($this->fetchEventRows($locationTable, $locationGeoColumns) as $row) {
|
||||||
$id = (int) ($row['event_id'] ?? 0);
|
$id = (int) ($row['event_id'] ?? 0);
|
||||||
|
|
||||||
@@ -151,7 +132,12 @@ class MapModuleDataProvider
|
|||||||
'longitude' => $coords['longitude'],
|
'longitude' => $coords['longitude'],
|
||||||
'extra' => [
|
'extra' => [
|
||||||
'locationTitle' => trim((string) ($row['location_title'] ?? '')),
|
'locationTitle' => trim((string) ($row['location_title'] ?? '')),
|
||||||
'startDate' => $this->formatStartDate((int) ($row['startDate'] ?? 0)),
|
'startDate' => $this->formatStartDateTime(
|
||||||
|
(int) ($row['startDate'] ?? 0),
|
||||||
|
(string) ($row['addTime'] ?? ''),
|
||||||
|
(int) ($row['startTime'] ?? 0),
|
||||||
|
),
|
||||||
|
'detailUrl' => $this->resolveEventDetailUrl($id),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -165,7 +151,7 @@ class MapModuleDataProvider
|
|||||||
*/
|
*/
|
||||||
public function getOrganizationTags(array $selectedTagIds = []): array
|
public function getOrganizationTags(array $selectedTagIds = []): array
|
||||||
{
|
{
|
||||||
if (!$this->tableExists('tl_tags')) {
|
if (!$this->tableExists('tl_tags') || !$this->tableExists('tl_tags_rel')) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,8 +169,13 @@ class MapModuleDataProvider
|
|||||||
|
|
||||||
$qb = $this->connection->createQueryBuilder();
|
$qb = $this->connection->createQueryBuilder();
|
||||||
$qb
|
$qb
|
||||||
->select('t.id', sprintf('t.%s AS label', $labelColumn))
|
->select('DISTINCT t.id', sprintf('t.%s AS label', $labelColumn))
|
||||||
->from('tl_tags', 't')
|
->from('tl_tags_rel', 'r')
|
||||||
|
->innerJoin('r', 'tl_tags', 't', 't.id = r.tag_id')
|
||||||
|
->andWhere('r.ptable = :organization_ptable')
|
||||||
|
->andWhere('(r.field = :organization_field OR r.field IS NULL OR r.field = \'\')')
|
||||||
|
->setParameter('organization_ptable', 'tl_organization')
|
||||||
|
->setParameter('organization_field', 'tags')
|
||||||
->orderBy(sprintf('t.%s', $labelColumn), 'ASC');
|
->orderBy(sprintf('t.%s', $labelColumn), 'ASC');
|
||||||
|
|
||||||
if ([] !== $selectedTagIds) {
|
if ([] !== $selectedTagIds) {
|
||||||
@@ -231,23 +222,36 @@ class MapModuleDataProvider
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$tagColumns = $this->getColumnMap('tl_tags');
|
||||||
|
$tagLabelColumn = isset($tagColumns['title']) ? 'title' : (isset($tagColumns['tag']) ? 'tag' : null);
|
||||||
|
|
||||||
|
if (null === $tagLabelColumn) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
$rows = $this->connection->executeQuery(
|
$rows = $this->connection->executeQuery(
|
||||||
'SELECT r.pid AS organization_id, r.tag_id, t.tag AS label
|
sprintf(
|
||||||
|
'SELECT r.pid AS organization_id, r.tag_id, t.%s AS label
|
||||||
FROM tl_tags_rel r
|
FROM tl_tags_rel r
|
||||||
INNER JOIN tl_tags t ON t.id = r.tag_id
|
INNER JOIN tl_tags t ON t.id = r.tag_id
|
||||||
WHERE r.ptable = ? AND r.field = ? AND r.pid IN (?)
|
WHERE r.ptable = ? AND r.field = ? AND r.pid IN (?)
|
||||||
ORDER BY r.pid ASC, r.tag_id ASC',
|
ORDER BY r.pid ASC, r.tag_id ASC',
|
||||||
|
$tagLabelColumn,
|
||||||
|
),
|
||||||
['tl_organization', 'tags', $organizationIds],
|
['tl_organization', 'tags', $organizationIds],
|
||||||
[ParameterType::STRING, ParameterType::STRING, ArrayParameterType::INTEGER],
|
[ParameterType::STRING, ParameterType::STRING, ArrayParameterType::INTEGER],
|
||||||
)->fetchAllAssociative();
|
)->fetchAllAssociative();
|
||||||
|
|
||||||
if ([] === $rows) {
|
if ([] === $rows) {
|
||||||
$rows = $this->connection->executeQuery(
|
$rows = $this->connection->executeQuery(
|
||||||
'SELECT r.pid AS organization_id, r.tag_id, t.tag AS label
|
sprintf(
|
||||||
|
'SELECT r.pid AS organization_id, r.tag_id, t.%s AS label
|
||||||
FROM tl_tags_rel r
|
FROM tl_tags_rel r
|
||||||
INNER JOIN tl_tags t ON t.id = r.tag_id
|
INNER JOIN tl_tags t ON t.id = r.tag_id
|
||||||
WHERE r.ptable = ? AND r.pid IN (?)
|
WHERE r.ptable = ? AND r.pid IN (?)
|
||||||
ORDER BY r.pid ASC, r.tag_id ASC',
|
ORDER BY r.pid ASC, r.tag_id ASC',
|
||||||
|
$tagLabelColumn,
|
||||||
|
),
|
||||||
['tl_organization', $organizationIds],
|
['tl_organization', $organizationIds],
|
||||||
[ParameterType::STRING, ArrayParameterType::INTEGER],
|
[ParameterType::STRING, ArrayParameterType::INTEGER],
|
||||||
)->fetchAllAssociative();
|
)->fetchAllAssociative();
|
||||||
@@ -261,13 +265,16 @@ class MapModuleDataProvider
|
|||||||
$tagId = (int) ($row['tag_id'] ?? 0);
|
$tagId = (int) ($row['tag_id'] ?? 0);
|
||||||
$label = trim((string) ($row['label'] ?? ''));
|
$label = trim((string) ($row['label'] ?? ''));
|
||||||
|
|
||||||
if ($organizationId <= 0 || $tagId <= 0 || '' === $label || isset($seen[$organizationId][$tagId])) {
|
if ($organizationId <= 0 || $tagId <= 0 || isset($seen[$organizationId][$tagId])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$seen[$organizationId][$tagId] = true;
|
$seen[$organizationId][$tagId] = true;
|
||||||
$map[$organizationId]['ids'][] = (string) $tagId;
|
$map[$organizationId]['ids'][] = (string) $tagId;
|
||||||
$map[$organizationId]['labels'][] = $label;
|
|
||||||
|
if ('' !== $label) {
|
||||||
|
$map[$organizationId]['labels'][] = $label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($map as $organizationId => $tagData) {
|
foreach ($map as $organizationId => $tagData) {
|
||||||
@@ -340,24 +347,6 @@ class MapModuleDataProvider
|
|||||||
return $qb->executeQuery()->fetchAllAssociative();
|
return $qb->executeQuery()->fetchAllAssociative();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return list<array<string,mixed>>
|
|
||||||
*/
|
|
||||||
private function fetchLocationRows(string $locationTable, array $locationGeoColumns): array
|
|
||||||
{
|
|
||||||
$qb = $this->connection->createQueryBuilder();
|
|
||||||
$qb
|
|
||||||
->select('l.id', 'l.title')
|
|
||||||
->addSelect(sprintf('l.%s AS latitude', $locationGeoColumns['latitude']))
|
|
||||||
->addSelect(sprintf('l.%s AS longitude', $locationGeoColumns['longitude']))
|
|
||||||
->from($locationTable, 'l')
|
|
||||||
->orderBy('l.id', 'ASC');
|
|
||||||
|
|
||||||
$this->applyPublicationConstraints($qb, 'l', $locationTable);
|
|
||||||
|
|
||||||
return $qb->executeQuery()->fetchAllAssociative();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<array<string,mixed>>
|
* @return list<array<string,mixed>>
|
||||||
*/
|
*/
|
||||||
@@ -376,6 +365,8 @@ class MapModuleDataProvider
|
|||||||
'e.id AS event_id',
|
'e.id AS event_id',
|
||||||
'e.title AS event_title',
|
'e.title AS event_title',
|
||||||
'e.startDate',
|
'e.startDate',
|
||||||
|
'e.addTime',
|
||||||
|
'e.startTime',
|
||||||
'l.title AS location_title',
|
'l.title AS location_title',
|
||||||
sprintf('l.%s AS latitude', $locationGeoColumns['latitude']),
|
sprintf('l.%s AS latitude', $locationGeoColumns['latitude']),
|
||||||
sprintf('l.%s AS longitude', $locationGeoColumns['longitude'])
|
sprintf('l.%s AS longitude', $locationGeoColumns['longitude'])
|
||||||
@@ -454,15 +445,53 @@ class MapModuleDataProvider
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatStartDate(int $timestamp): string
|
private function formatStartDateTime(int $startDate, string $addTime, int $startTime): string
|
||||||
{
|
{
|
||||||
if ($timestamp <= 0) {
|
if ($startDate <= 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$format = (string) Config::get('dateFormat');
|
$formattedDate = date('d.m.Y', $startDate);
|
||||||
|
|
||||||
return Date::parse('' !== $format ? $format : 'd.m.Y', $timestamp);
|
if ('1' !== $addTime || $startTime <= 0) {
|
||||||
|
return $formattedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('%s %s Uhr', $formattedDate, date('H:i', $startTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveEventDetailUrl(int $eventId): string
|
||||||
|
{
|
||||||
|
if ($eventId <= 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventModel = $this->framework
|
||||||
|
->getAdapter(CalendarEventsModel::class)
|
||||||
|
->findById($eventId);
|
||||||
|
|
||||||
|
if (null === $eventModel) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (string) $this->urlGenerator->generate($eventModel, [], UrlGeneratorInterface::ABSOLUTE_PATH);
|
||||||
|
} catch (ExceptionInterface) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildOrganizationDetailUrl(string $baseUrl, int $organizationId): string
|
||||||
|
{
|
||||||
|
$normalizedBaseUrl = trim($baseUrl);
|
||||||
|
|
||||||
|
if ('' === $normalizedBaseUrl || $organizationId <= 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$separator = str_contains($normalizedBaseUrl, '?') ? '&' : '?';
|
||||||
|
|
||||||
|
return sprintf('%s%sshow=%d', $normalizedBaseUrl, $separator, $organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace MummertMedia\EventManagerBundle\Service;
|
namespace Mummert\EventManagerBundle\Service;
|
||||||
|
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use Doctrine\DBAL\ParameterType;
|
use Doctrine\DBAL\ParameterType;
|
||||||
@@ -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'] ?? '',
|
||||||
|
|||||||