diff --git a/CHANGELOG.md b/CHANGELOG.md index 9034d3598..e69fab90d 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* [PR-611](https://github.com/itk-dev/deltag.aarhus.dk/pull/611) + Added windowed dot navigation on mobile horizontal timeline view +* [PR-610](https://github.com/itk-dev/deltag.aarhus.dk/pull/610) + Added hover effect to reveal accent colors on upcoming timeline cards +* [PR-593](https://github.com/itk-dev/deltag.aarhus.dk/pull/593) + * Add project timeline + * Add project reference fields to several node types to display on timeline + * Add paragraph for adding custom elements to timeline + * Add CLAUDE.md file to project + ## [4.15.0] - 2026-02-25 * [PR-590](https://github.com/itk-dev/deltag.aarhus.dk/pull/590) @@ -37,6 +47,13 @@ Versioning](https://semver.org/spec/v2.0.0.html). * [PR-591](https://github.com/itk-dev/deltag.aarhus.dk/pull/591) * Cleaned up local OIDC setup * Added test server OIDC setup +<<<<<<< HEAD +* [PR-590](https://github.com/itk-dev/deltag.aarhus.dk/pull/590) + Deleted old timeline. +======= + +>>>>>>> develop + * [PR-587](https://github.com/itk-dev/deltag.aarhus.dk/pull/587) Cleaned up translations * [PR-586](https://github.com/itk-dev/hoeringsportal/pull/586) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..6536800d2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,160 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Høringsportal (deltag.aarhus.dk) is a Drupal 10 civic engagement platform for Aarhus municipality. +It enables citizens to participate in public hearings, submit citizen proposals, attend public meetings, +and engage with local projects. + +## Development Commands + +This project uses [Task](https://taskfile.dev/) for all development workflows. Run `task` to see available commands. + +### Essential Commands + +```bash +# Initial setup (resets database) +task site-install + +# Update existing installation +task site-update + +# Build theme assets +task assets-build + +# Watch for changes during development +task assets-watch + +# Load test fixtures +task fixtures:load + +# Run Drush commands +task drush -- + +# Access container +task compose -- exec phpfpm bash +``` + +### Code Quality + +```bash +# Apply all coding standards (PHP, JS, CSS, Twig, YAML, Markdown) +task coding-standards:apply + +# Check all coding standards +task coding-standards:check + +# Static analysis (PHPStan) +task code-analysis + +# Individual checks +task coding-standards:php:check +task coding-standards:twig:check +task coding-standards:javascript:check +task coding-standards:styles:check +``` + +### Testing + +Playwright tests are located in `playwright/` directory: + +```bash +docker compose --profile test run --rm playwright npx playwright test +``` + +## Architecture + +### Directory Structure + +- `web/modules/custom/` - Custom Drupal modules +- `web/themes/custom/hoeringsportal/` - Main theme (Bootstrap 5, Webpack Encore) +- `web/themes/custom/hoeringsportal_admin/` - Admin theme +- `config/sync/` - Drupal configuration + +### Core Custom Modules + +- **hoeringsportal_hearing** - Hearing (høring) content type and functionality +- **hoeringsportal_citizen_proposal** - Citizen proposal system with OpenID Connect authentication +- **hoeringsportal_public_meeting** - Public meetings with Pretix integration for ticketing +- **hoeringsportal_activity** - Extends public meetings to include other activity types +- **hoeringsportal_project** - Project content type for civic engagement projects +- **hoeringsportal_deskpro** - Deskpro helpdesk integration for hearing replies +- **hoeringsportal_data** - Data helpers and API endpoints +- **hoeringsportal_dialogue** - Dialogue/discussion features with comments +- **hoeringsportal_anonymous_edit** - Allow anonymous users to edit their submissions +- **hoeringsportal_openid_connect** - OpenID Connect authentication customizations + +### External Integrations + +- **Pretix** - Event ticketing for public meetings (docker-compose.pretix.yml) +- **Deskpro** - Helpdesk for hearing submissions +- **OpenID Connect** - Citizen authentication (docker-compose.oidc.yml) +- **ClamAV** - Virus scanning for uploads +- **Serviceplatformen** - Danish government services integration + +### Theme Architecture + +The theme uses Webpack Encore for asset building: + +```bash +# Theme location +web/themes/custom/hoeringsportal/ + +# Build commands +npm install --prefix web/themes/custom/hoeringsportal +npm run build --prefix web/themes/custom/hoeringsportal +``` + +CSS uses SCSS with Bootstrap 5 and CSS custom properties for color management. + +## Coding Standards + +- PHP follows Drupal coding standards (phpcs.xml.dist) +- PHPStan level 0 for custom modules, level 9 for hoeringsportal_audit_log +- Twig uses twig-cs-fixer +- JavaScript and CSS use Prettier +- YAML uses Prettier + +## Docker Services + +Primary services (docker-compose.yml): + +- phpfpm (PHP 8.3) +- nginx +- mariadb +- memcached +- mail (Mailpit) + +Optional profiles: + +- `pretix` - Event ticketing system +- `test` - Playwright testing +- `dev` - Code quality tools (markdownlint, prettier) + +Start optional services: + +```bash +PROFILES=pretix task compose -- up --detach +``` + +## Configuration + +Local settings go in `web/sites/default/settings.local.php`. +Environment variables can be set in `.env.local`. + +Key settings: + +- `TASK_DOCKER_COMPOSE_PROFILES` - Docker profiles to auto-start +- `TASK_ASSETS_SKIP_BUILD` - Skip asset building on site-update + +## Translations + +Import custom translations: + +```bash +task translations:import +``` + +Translation files are in `translations/` and managed via Drush locale commands. diff --git a/Taskfile.yml b/Taskfile.yml index 74b77f02e..757a0a2cd 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -26,9 +26,9 @@ includes: - hoeringsportal_hearing # - hoeringsportal_misc # - hoeringsportal_openid_connect - # - hoeringsportal_project + - hoeringsportal_project - hoeringsportal_public_meeting - - hoeringsportal_quicklinks + # - hoeringsportal_quicklinks # - hoeringsportal_test_delta_sync_fixtures - itk_admin diff --git a/config/sync/core.entity_form_display.node.course.default.yml b/config/sync/core.entity_form_display.node.course.default.yml index 45a242973..4c97cbf62 100644 --- a/config/sync/core.entity_form_display.node.course.default.yml +++ b/config/sync/core.entity_form_display.node.course.default.yml @@ -12,9 +12,11 @@ dependencies: - field.field.node.course.field_content_state - field.field.node.course.field_department - field.field.node.course.field_first_meeting_time + - field.field.node.course.field_hide_in_timeline - field.field.node.course.field_hide_time - field.field.node.course.field_last_meeting_time - field.field.node.course.field_map + - field.field.node.course.field_project_reference - field.field.node.course.field_teaser - field.field.node.course.field_top_images - field.field.node.course.field_type @@ -32,13 +34,14 @@ third_party_settings: - field_first_meeting_time - field_last_meeting_time - field_hide_time + - group_project_reference - field_department - field_type - field_area label: Informationer region: content parent_name: '' - weight: 8 + weight: 7 format_type: details format_settings: classes: '' @@ -55,7 +58,24 @@ third_party_settings: label: Lokation region: content parent_name: '' - weight: 3 + weight: 4 + format_type: details + format_settings: + classes: '' + show_empty_fields: false + id: '' + label_as_html: false + open: true + description: '' + required_fields: false + group_project_reference: + children: + - field_project_reference + - field_hide_in_timeline + label: 'Projekt reference' + region: hidden + parent_name: group_information + weight: 14 format_type: details format_settings: classes: '' @@ -94,13 +114,13 @@ content: third_party_settings: { } field_area: type: options_buttons - weight: 16 + weight: 17 region: content settings: { } third_party_settings: { } field_content_sections: type: paragraphs - weight: 6 + weight: 5 region: content settings: title: Paragraph @@ -120,7 +140,7 @@ content: third_party_settings: { } field_department: type: options_buttons - weight: 14 + weight: 15 region: content settings: { } third_party_settings: { } @@ -130,6 +150,13 @@ content: region: content settings: { } third_party_settings: { } + field_hide_in_timeline: + type: boolean_checkbox + weight: 10 + region: content + settings: + display_label: true + third_party_settings: { } field_hide_time: type: boolean_checkbox weight: 13 @@ -145,7 +172,7 @@ content: third_party_settings: { } field_map: type: hoeringsportal_data_map_default - weight: 7 + weight: 6 region: content settings: available_types: @@ -153,9 +180,19 @@ content: localplanids: 0 localplanids_node: 0 third_party_settings: { } + field_project_reference: + type: entity_reference_autocomplete + weight: 9 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } field_teaser: type: string_textarea - weight: 2 + weight: 3 region: content settings: rows: 5 @@ -178,13 +215,13 @@ content: third_party_settings: { } field_type: type: options_buttons - weight: 15 + weight: 16 region: content settings: { } third_party_settings: { } status: type: boolean_checkbox - weight: 9 + weight: 8 region: content settings: display_label: true diff --git a/config/sync/core.entity_form_display.node.decision.default.yml b/config/sync/core.entity_form_display.node.decision.default.yml index d65829968..9c87e9a05 100644 --- a/config/sync/core.entity_form_display.node.decision.default.yml +++ b/config/sync/core.entity_form_display.node.decision.default.yml @@ -9,7 +9,9 @@ dependencies: - field.field.node.decision.field_decision - field.field.node.decision.field_decision_date - field.field.node.decision.field_department + - field.field.node.decision.field_hide_in_timeline - field.field.node.decision.field_media_document + - field.field.node.decision.field_project_reference - field.field.node.decision.field_related_content - field.field.node.decision.field_teaser - field.field.node.decision.field_top_images @@ -26,6 +28,7 @@ third_party_settings: children: - field_decision - field_decision_date + - group_project_reference - field_department - field_area - field_type @@ -42,6 +45,23 @@ third_party_settings: open: true description: '' required_fields: false + group_project_reference: + children: + - field_project_reference + - field_hide_in_timeline + label: 'Projekt reference' + region: hidden + parent_name: group_information + weight: 4 + format_type: details + format_settings: + classes: '' + show_empty_fields: false + id: '' + label_as_html: false + open: true + description: '' + required_fields: false id: node.decision.default targetEntityType: node bundle: decision @@ -49,7 +69,7 @@ mode: default content: field_area: type: options_buttons - weight: 5 + weight: 6 region: content settings: { } third_party_settings: { } @@ -87,10 +107,17 @@ content: third_party_settings: { } field_department: type: options_buttons - weight: 4 + weight: 5 region: content settings: { } third_party_settings: { } + field_hide_in_timeline: + type: boolean_checkbox + weight: 10 + region: content + settings: + display_label: true + third_party_settings: { } field_media_document: type: entity_browser_entity_reference weight: 4 @@ -106,6 +133,16 @@ content: view_mode: node_form_display selection_mode: selection_append third_party_settings: { } + field_project_reference: + type: entity_reference_autocomplete + weight: 9 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } field_related_content: type: entity_reference_autocomplete weight: 5 @@ -141,7 +178,7 @@ content: third_party_settings: { } field_type: type: options_buttons - weight: 6 + weight: 7 region: content settings: { } third_party_settings: { } diff --git a/config/sync/core.entity_form_display.node.dialogue.default.yml b/config/sync/core.entity_form_display.node.dialogue.default.yml index ad962a8e5..5c6b84ddc 100644 --- a/config/sync/core.entity_form_display.node.dialogue.default.yml +++ b/config/sync/core.entity_form_display.node.dialogue.default.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -25,6 +27,7 @@ third_party_settings: group_information: children: - field_hidden_dialogue + - group_project_reference - field_department - field_type - field_area @@ -59,6 +62,23 @@ third_party_settings: open: true description: '' required_fields: false + group_project_reference: + children: + - field_project_reference + - field_hide_in_timeline + label: 'Projektreference' + region: hidden + parent_name: group_information + weight: 32 + format_type: details + format_settings: + classes: '' + show_empty_fields: false + id: '' + label_as_html: false + open: true + description: '' + required_fields: false id: node.dialogue.default targetEntityType: node bundle: dialogue @@ -66,7 +86,7 @@ mode: default content: field_area: type: options_buttons - weight: 34 + weight: 35 region: content settings: { } third_party_settings: { } @@ -92,7 +112,7 @@ content: third_party_settings: { } field_department: type: options_buttons - weight: 32 + weight: 33 region: content settings: { } third_party_settings: { } @@ -129,6 +149,23 @@ content: settings: display_label: true third_party_settings: { } + field_hide_in_timeline: + type: boolean_checkbox + weight: 8 + region: content + settings: + display_label: true + third_party_settings: { } + field_project_reference: + type: entity_reference_autocomplete + weight: 7 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } field_teaser: type: string_textarea weight: 2 @@ -154,7 +191,7 @@ content: third_party_settings: { } field_type: type: options_buttons - weight: 33 + weight: 34 region: content settings: { } third_party_settings: { } diff --git a/config/sync/core.entity_form_display.node.hearing.default.yml b/config/sync/core.entity_form_display.node.hearing.default.yml index 4f0dbbf2c..31cd794ae 100644 --- a/config/sync/core.entity_form_display.node.hearing.default.yml +++ b/config/sync/core.entity_form_display.node.hearing.default.yml @@ -16,12 +16,14 @@ dependencies: - field.field.node.hearing.field_getorganized_case_id - field.field.node.hearing.field_hearing_ticket_add - field.field.node.hearing.field_hearing_ticket_list + - field.field.node.hearing.field_hide_in_timeline - field.field.node.hearing.field_lokalplaner - field.field.node.hearing.field_map - field.field.node.hearing.field_map_display - field.field.node.hearing.field_media_document - field.field.node.hearing.field_media_image - field.field.node.hearing.field_more_info + - field.field.node.hearing.field_project_reference - field.field.node.hearing.field_reply_deadline - field.field.node.hearing.field_start_date - field.field.node.hearing.field_tags @@ -42,6 +44,7 @@ third_party_settings: field_group: group_information: children: + - group_project_reference - field_department - field_type - field_area @@ -49,11 +52,10 @@ third_party_settings: - field_reply_deadline - field_delete_date - field_lokalplaner - - field_project_reference label: Informationer region: content parent_name: '' - weight: 7 + weight: 6 format_type: details format_settings: classes: '' @@ -66,7 +68,7 @@ third_party_settings: label: Systemindstillinger region: content parent_name: '' - weight: 9 + weight: 8 format_type: details format_settings: classes: '' @@ -80,7 +82,7 @@ third_party_settings: label: Deskpro region: content parent_name: '' - weight: 8 + weight: 7 format_type: details format_settings: classes: '' @@ -93,7 +95,7 @@ third_party_settings: label: eDoc region: content parent_name: '' - weight: 11 + weight: 10 format_type: details format_settings: classes: '' @@ -159,6 +161,23 @@ third_party_settings: open: false description: '' required_fields: true + group_project_reference: + children: + - field_project_reference + - field_hide_in_timeline + label: 'Projekt reference' + region: content + parent_name: group_information + weight: 26 + format_type: details + format_settings: + classes: '' + show_empty_fields: false + id: '' + label_as_html: false + open: true + description: '' + required_fields: false id: node.hearing.default targetEntityType: node bundle: hearing @@ -166,7 +185,7 @@ mode: default content: field_area: type: options_buttons - weight: 38 + weight: 29 region: content settings: { } third_party_settings: { } @@ -180,13 +199,13 @@ content: third_party_settings: { } field_delete_date: type: datetime_default - weight: 41 + weight: 32 region: content settings: { } third_party_settings: { } field_department: type: options_buttons - weight: 36 + weight: 27 region: content settings: { } third_party_settings: { } @@ -200,7 +219,7 @@ content: third_party_settings: { } field_deskpro_agent_email: type: email_default - weight: 35 + weight: 36 region: content settings: placeholder: '' @@ -220,7 +239,7 @@ content: maxlength_js_enforce: false field_edoc_casefile_id: type: string_textfield - weight: 14 + weight: 36 region: content settings: size: 60 @@ -250,9 +269,16 @@ content: placeholder_url: 'Url til visning af høringssvar i eksternt høringssvarsystem' placeholder_title: '' third_party_settings: { } + field_hide_in_timeline: + type: boolean_checkbox + weight: 19 + region: content + settings: + display_label: true + third_party_settings: { } field_lokalplaner: type: hoeringsportal_data_localplan_default - weight: 42 + weight: 33 region: content settings: { } third_party_settings: { } @@ -298,7 +324,7 @@ content: third_party_settings: { } field_more_info: type: text_textarea - weight: 14 + weight: 19 region: content settings: rows: 5 @@ -311,15 +337,25 @@ content: allowed_formats: hide_help: '0' hide_guidelines: '0' + field_project_reference: + type: entity_reference_autocomplete + weight: 18 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } field_reply_deadline: type: datetime_default - weight: 40 + weight: 31 region: content settings: { } third_party_settings: { } field_start_date: type: datetime_default - weight: 39 + weight: 30 region: content settings: { } third_party_settings: { } @@ -333,19 +369,19 @@ content: third_party_settings: { } field_type: type: options_buttons - weight: 37 + weight: 28 region: content settings: { } third_party_settings: { } published_at: type: publication_date_timestamp - weight: 10 + weight: 9 region: content settings: { } third_party_settings: { } status: type: boolean_checkbox - weight: 5 + weight: 36 region: content settings: display_label: true diff --git a/config/sync/core.entity_form_display.node.project_main_page.default.yml b/config/sync/core.entity_form_display.node.project_main_page.default.yml index ed820e2cb..52fd029ea 100644 --- a/config/sync/core.entity_form_display.node.project_main_page.default.yml +++ b/config/sync/core.entity_form_display.node.project_main_page.default.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.project_main_page.field_project_image - field.field.node.project_main_page.field_project_status - field.field.node.project_main_page.field_short_description + - field.field.node.project_main_page.field_show_timeline + - field.field.node.project_main_page.field_timeline - field.field.node.project_main_page.field_type - node.type.project_main_page module: @@ -32,7 +34,7 @@ third_party_settings: label: Informationer region: content parent_name: '' - weight: 5 + weight: 8 format_type: details_sidebar format_settings: classes: '' @@ -43,6 +45,23 @@ third_party_settings: description: '' required_fields: true weight: 0 + group_timeline: + children: + - field_show_timeline + - field_timeline + label: Tidslinje + region: content + parent_name: '' + weight: 6 + format_type: details + format_settings: + classes: '' + show_empty_fields: false + id: '' + label_as_html: false + open: true + description: '' + required_fields: false id: node.project_main_page.default targetEntityType: node bundle: project_main_page @@ -50,19 +69,19 @@ mode: default content: created: type: datetime_timestamp - weight: 9 + weight: 11 region: content settings: { } third_party_settings: { } field_area: type: options_buttons - weight: 10 + weight: 13 region: content settings: { } third_party_settings: { } field_content_sections: type: paragraphs - weight: 6 + weight: 5 region: content settings: title: Paragraph @@ -82,7 +101,7 @@ content: third_party_settings: { } field_department: type: options_buttons - weight: 8 + weight: 11 region: content settings: { } third_party_settings: { } @@ -132,48 +151,75 @@ content: maxlength_js: null maxlength_js_label: 'Content limited to @limit characters, remaining: @remaining' maxlength_js_enforce: false + field_show_timeline: + type: boolean_checkbox + weight: 9 + region: content + settings: + display_label: true + third_party_settings: { } + field_timeline: + type: paragraphs + weight: 10 + region: content + settings: + title: Paragraph + title_plural: Paragraphs + edit_mode: closed_expand_nested + closed_mode: summary + autocollapse: none + closed_mode_threshold: 1 + add_mode: dropdown + form_display_mode: default + default_paragraph_type: _none + features: + add_above: '0' + collapse_edit_all: collapse_edit_all + convert: '0' + duplicate: duplicate + third_party_settings: { } field_type: type: options_buttons - weight: 9 + weight: 12 region: content settings: { } third_party_settings: { } langcode: type: language_select - weight: 7 + weight: 9 region: content settings: include_locked: true third_party_settings: { } path: type: path - weight: 13 + weight: 15 region: content settings: { } third_party_settings: { } promote: type: boolean_checkbox - weight: 11 + weight: 13 region: content settings: display_label: true third_party_settings: { } published_at: type: publication_date_timestamp - weight: 10 + weight: 12 region: content settings: { } third_party_settings: { } status: type: boolean_checkbox - weight: 15 + weight: 17 region: content settings: display_label: true third_party_settings: { } sticky: type: boolean_checkbox - weight: 12 + weight: 14 region: content settings: display_label: true @@ -192,7 +238,7 @@ content: maxlength_js_enforce: false uid: type: entity_reference_autocomplete - weight: 8 + weight: 10 region: content settings: match_operator: CONTAINS @@ -201,7 +247,7 @@ content: placeholder: '' third_party_settings: { } url_redirects: - weight: 14 + weight: 16 region: content settings: { } third_party_settings: { } diff --git a/config/sync/core.entity_form_display.node.public_meeting.default.yml b/config/sync/core.entity_form_display.node.public_meeting.default.yml index 0c866f8ab..a7c05f4e9 100644 --- a/config/sync/core.entity_form_display.node.public_meeting.default.yml +++ b/config/sync/core.entity_form_display.node.public_meeting.default.yml @@ -17,6 +17,7 @@ dependencies: - field.field.node.public_meeting.field_email_address - field.field.node.public_meeting.field_first_meeting_time - field.field.node.public_meeting.field_hidden_signup + - field.field.node.public_meeting.field_hide_in_timeline - field.field.node.public_meeting.field_last_meeting_time - field.field.node.public_meeting.field_last_meeting_time_end - field.field.node.public_meeting.field_map @@ -24,6 +25,7 @@ dependencies: - field.field.node.public_meeting.field_media_image_single - field.field.node.public_meeting.field_pretix_dates - field.field.node.public_meeting.field_pretix_event_settings + - field.field.node.public_meeting.field_project_reference - field.field.node.public_meeting.field_public_meeting_cancelled - field.field.node.public_meeting.field_registration_deadline - field.field.node.public_meeting.field_section @@ -47,6 +49,7 @@ third_party_settings: field_group: group_information: children: + - group_project_reference - field_department - field_type - field_area @@ -130,6 +133,23 @@ third_party_settings: open: false description: '' required_fields: true + group_project_reference: + children: + - field_project_reference + - field_hide_in_timeline + label: 'Projekt reference' + region: hidden + parent_name: group_information + weight: 11 + format_type: details + format_settings: + classes: '' + show_empty_fields: false + id: '' + label_as_html: false + open: true + description: '' + required_fields: false id: node.public_meeting.default targetEntityType: node bundle: public_meeting @@ -137,7 +157,7 @@ mode: default content: field_activity_location: type: string_textfield - weight: 19 + weight: 20 region: content settings: size: 60 @@ -151,7 +171,7 @@ content: third_party_settings: { } field_address: type: string_textfield - weight: 20 + weight: 21 region: content settings: size: 60 @@ -159,7 +179,7 @@ content: third_party_settings: { } field_area: type: options_buttons - weight: 11 + weight: 14 region: content settings: { } third_party_settings: { } @@ -187,7 +207,7 @@ content: third_party_settings: { } field_department: type: options_buttons - weight: 9 + weight: 12 region: content settings: { } third_party_settings: { } @@ -201,7 +221,7 @@ content: third_party_settings: { } field_email_address: type: email_default - weight: 11 + weight: 12 region: content settings: placeholder: '' @@ -214,6 +234,13 @@ content: settings: display_label: true third_party_settings: { } + field_hide_in_timeline: + type: boolean_checkbox + weight: 18 + region: content + settings: + display_label: true + third_party_settings: { } field_last_meeting_time: type: datetime_default weight: 17 @@ -281,6 +308,16 @@ content: region: content settings: { } third_party_settings: { } + field_project_reference: + type: entity_reference_autocomplete + weight: 17 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } field_public_meeting_cancelled: type: boolean_checkbox weight: 0 @@ -338,7 +375,7 @@ content: third_party_settings: { } field_type: type: options_buttons - weight: 10 + weight: 13 region: content settings: { } third_party_settings: { } diff --git a/config/sync/core.entity_form_display.paragraph.timeline_note.default.yml b/config/sync/core.entity_form_display.paragraph.timeline_note.default.yml new file mode 100644 index 000000000..4dffe9e16 --- /dev/null +++ b/config/sync/core.entity_form_display.paragraph.timeline_note.default.yml @@ -0,0 +1,78 @@ +uuid: 3d7eea78-8277-4f02-9029-adc6082a6246 +langcode: da +status: true +dependencies: + config: + - entity_browser.browser.itk_image_browser + - field.field.paragraph.timeline_note.field_date + - field.field.paragraph.timeline_note.field_external_link + - field.field.paragraph.timeline_note.field_note + - field.field.paragraph.timeline_note.field_paragraph_image + - field.field.paragraph.timeline_note.field_subtitle + - field.field.paragraph.timeline_note.field_title + - paragraphs.paragraphs_type.timeline_note + module: + - datetime + - entity_browser + - link +id: paragraph.timeline_note.default +targetEntityType: paragraph +bundle: timeline_note +mode: default +content: + field_date: + type: datetime_default + weight: 3 + region: content + settings: { } + third_party_settings: { } + field_external_link: + type: link_default + weight: 5 + region: content + settings: + placeholder_url: '' + placeholder_title: '' + third_party_settings: { } + field_note: + type: string_textarea + weight: 4 + region: content + settings: + rows: 5 + placeholder: '' + third_party_settings: { } + field_paragraph_image: + type: entity_browser_entity_reference + weight: 2 + region: content + settings: + entity_browser: itk_image_browser + field_widget_display: rendered_entity + field_widget_edit: false + field_widget_remove: true + field_widget_replace: false + open: true + field_widget_display_settings: + view_mode: content_display + selection_mode: selection_append + third_party_settings: { } + field_subtitle: + type: string_textfield + weight: 1 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + field_title: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: + created: true + status: true diff --git a/config/sync/core.entity_view_display.node.course.default.yml b/config/sync/core.entity_view_display.node.course.default.yml index 56b1ca060..86fb1d2e4 100644 --- a/config/sync/core.entity_view_display.node.course.default.yml +++ b/config/sync/core.entity_view_display.node.course.default.yml @@ -11,9 +11,11 @@ dependencies: - field.field.node.course.field_content_state - field.field.node.course.field_department - field.field.node.course.field_first_meeting_time + - field.field.node.course.field_hide_in_timeline - field.field.node.course.field_hide_time - field.field.node.course.field_last_meeting_time - field.field.node.course.field_map + - field.field.node.course.field_project_reference - field.field.node.course.field_teaser - field.field.node.course.field_top_images - field.field.node.course.field_type @@ -32,7 +34,7 @@ content: settings: link_to_entity: false third_party_settings: { } - weight: 7 + weight: 8 region: content field_activity_type: type: entity_reference_label @@ -40,7 +42,7 @@ content: settings: link: false third_party_settings: { } - weight: 16 + weight: 9 region: content field_address: type: string @@ -48,7 +50,7 @@ content: settings: link_to_entity: false third_party_settings: { } - weight: 6 + weight: 7 region: content field_area: type: entity_reference_label @@ -56,7 +58,7 @@ content: settings: link: false third_party_settings: { } - weight: 2 + weight: 3 region: content field_content_sections: type: entity_reference_revisions_entity_view @@ -65,7 +67,7 @@ content: view_mode: default link: '' third_party_settings: { } - weight: 4 + weight: 5 region: content field_department: type: entity_reference_label @@ -83,14 +85,14 @@ content: format_custom_false: '' format_custom_true: '' third_party_settings: { } - weight: 5 + weight: 6 region: content field_teaser: type: basic_string label: hidden settings: { } third_party_settings: { } - weight: 1 + weight: 2 region: content field_top_images: type: entity_reference_entity_view @@ -107,13 +109,15 @@ content: settings: link: false third_party_settings: { } - weight: 3 + weight: 4 region: content hidden: field_content_state: true field_first_meeting_time: true + field_hide_in_timeline: true field_last_meeting_time: true field_map: true + field_project_reference: true langcode: true links: true published_at: true diff --git a/config/sync/core.entity_view_display.node.course.full.yml b/config/sync/core.entity_view_display.node.course.full.yml index 53b0a6b41..daafe0dd2 100644 --- a/config/sync/core.entity_view_display.node.course.full.yml +++ b/config/sync/core.entity_view_display.node.course.full.yml @@ -12,9 +12,11 @@ dependencies: - field.field.node.course.field_content_state - field.field.node.course.field_department - field.field.node.course.field_first_meeting_time + - field.field.node.course.field_hide_in_timeline - field.field.node.course.field_hide_time - field.field.node.course.field_last_meeting_time - field.field.node.course.field_map + - field.field.node.course.field_project_reference - field.field.node.course.field_teaser - field.field.node.course.field_top_images - field.field.node.course.field_type @@ -129,7 +131,9 @@ content: region: content hidden: field_content_state: true + field_hide_in_timeline: true field_hide_time: true + field_project_reference: true langcode: true links: true published_at: true diff --git a/config/sync/core.entity_view_display.node.course.hearing_ticket_add.yml b/config/sync/core.entity_view_display.node.course.hearing_ticket_add.yml index 5f543526e..da89f8baf 100644 --- a/config/sync/core.entity_view_display.node.course.hearing_ticket_add.yml +++ b/config/sync/core.entity_view_display.node.course.hearing_ticket_add.yml @@ -12,9 +12,11 @@ dependencies: - field.field.node.course.field_content_state - field.field.node.course.field_department - field.field.node.course.field_first_meeting_time + - field.field.node.course.field_hide_in_timeline - field.field.node.course.field_hide_time - field.field.node.course.field_last_meeting_time - field.field.node.course.field_map + - field.field.node.course.field_project_reference - field.field.node.course.field_teaser - field.field.node.course.field_top_images - field.field.node.course.field_type @@ -60,9 +62,11 @@ hidden: field_content_state: true field_department: true field_first_meeting_time: true + field_hide_in_timeline: true field_hide_time: true field_last_meeting_time: true field_map: true + field_project_reference: true field_teaser: true field_top_images: true langcode: true diff --git a/config/sync/core.entity_view_display.node.course.hearing_ticket_view.yml b/config/sync/core.entity_view_display.node.course.hearing_ticket_view.yml index 14498e39d..75b9aa1d2 100644 --- a/config/sync/core.entity_view_display.node.course.hearing_ticket_view.yml +++ b/config/sync/core.entity_view_display.node.course.hearing_ticket_view.yml @@ -12,9 +12,11 @@ dependencies: - field.field.node.course.field_content_state - field.field.node.course.field_department - field.field.node.course.field_first_meeting_time + - field.field.node.course.field_hide_in_timeline - field.field.node.course.field_hide_time - field.field.node.course.field_last_meeting_time - field.field.node.course.field_map + - field.field.node.course.field_project_reference - field.field.node.course.field_teaser - field.field.node.course.field_top_images - field.field.node.course.field_type @@ -60,9 +62,11 @@ hidden: field_content_state: true field_department: true field_first_meeting_time: true + field_hide_in_timeline: true field_hide_time: true field_last_meeting_time: true field_map: true + field_project_reference: true field_teaser: true field_top_images: true langcode: true diff --git a/config/sync/core.entity_view_display.node.course.list_display.yml b/config/sync/core.entity_view_display.node.course.list_display.yml index 79cb8bbc3..4d78ec23a 100644 --- a/config/sync/core.entity_view_display.node.course.list_display.yml +++ b/config/sync/core.entity_view_display.node.course.list_display.yml @@ -12,9 +12,11 @@ dependencies: - field.field.node.course.field_content_state - field.field.node.course.field_department - field.field.node.course.field_first_meeting_time + - field.field.node.course.field_hide_in_timeline - field.field.node.course.field_hide_time - field.field.node.course.field_last_meeting_time - field.field.node.course.field_map + - field.field.node.course.field_project_reference - field.field.node.course.field_teaser - field.field.node.course.field_top_images - field.field.node.course.field_type @@ -60,9 +62,11 @@ hidden: field_content_state: true field_department: true field_first_meeting_time: true + field_hide_in_timeline: true field_hide_time: true field_last_meeting_time: true field_map: true + field_project_reference: true field_teaser: true field_top_images: true langcode: true diff --git a/config/sync/core.entity_view_display.node.course.search_result.yml b/config/sync/core.entity_view_display.node.course.search_result.yml index ce3fde8fd..937b9edb1 100644 --- a/config/sync/core.entity_view_display.node.course.search_result.yml +++ b/config/sync/core.entity_view_display.node.course.search_result.yml @@ -12,9 +12,11 @@ dependencies: - field.field.node.course.field_content_state - field.field.node.course.field_department - field.field.node.course.field_first_meeting_time + - field.field.node.course.field_hide_in_timeline - field.field.node.course.field_hide_time - field.field.node.course.field_last_meeting_time - field.field.node.course.field_map + - field.field.node.course.field_project_reference - field.field.node.course.field_teaser - field.field.node.course.field_top_images - field.field.node.course.field_type @@ -83,9 +85,11 @@ hidden: field_address: true field_content_state: true field_first_meeting_time: true + field_hide_in_timeline: true field_hide_time: true field_last_meeting_time: true field_map: true + field_project_reference: true field_top_images: true langcode: true published_at: true diff --git a/config/sync/core.entity_view_display.node.course.teaser.yml b/config/sync/core.entity_view_display.node.course.teaser.yml index b8c4a2421..1262e5ee1 100644 --- a/config/sync/core.entity_view_display.node.course.teaser.yml +++ b/config/sync/core.entity_view_display.node.course.teaser.yml @@ -12,9 +12,11 @@ dependencies: - field.field.node.course.field_content_state - field.field.node.course.field_department - field.field.node.course.field_first_meeting_time + - field.field.node.course.field_hide_in_timeline - field.field.node.course.field_hide_time - field.field.node.course.field_last_meeting_time - field.field.node.course.field_map + - field.field.node.course.field_project_reference - field.field.node.course.field_teaser - field.field.node.course.field_top_images - field.field.node.course.field_type @@ -101,8 +103,10 @@ hidden: field_address: true field_content_sections: true field_department: true + field_hide_in_timeline: true field_hide_time: true field_map: true + field_project_reference: true langcode: true published_at: true search_api_excerpt: true diff --git a/config/sync/core.entity_view_display.node.decision.default.yml b/config/sync/core.entity_view_display.node.decision.default.yml index 8157fa4b9..009b2a599 100644 --- a/config/sync/core.entity_view_display.node.decision.default.yml +++ b/config/sync/core.entity_view_display.node.decision.default.yml @@ -8,7 +8,9 @@ dependencies: - field.field.node.decision.field_decision - field.field.node.decision.field_decision_date - field.field.node.decision.field_department + - field.field.node.decision.field_hide_in_timeline - field.field.node.decision.field_media_document + - field.field.node.decision.field_project_reference - field.field.node.decision.field_related_content - field.field.node.decision.field_teaser - field.field.node.decision.field_top_images @@ -117,6 +119,8 @@ content: weight: 6 region: content hidden: + field_hide_in_timeline: true + field_project_reference: true langcode: true published_at: true search_api_excerpt: true diff --git a/config/sync/core.entity_view_display.node.decision.full.yml b/config/sync/core.entity_view_display.node.decision.full.yml index 5a9afac9e..a801d48a8 100644 --- a/config/sync/core.entity_view_display.node.decision.full.yml +++ b/config/sync/core.entity_view_display.node.decision.full.yml @@ -9,7 +9,9 @@ dependencies: - field.field.node.decision.field_decision - field.field.node.decision.field_decision_date - field.field.node.decision.field_department + - field.field.node.decision.field_hide_in_timeline - field.field.node.decision.field_media_document + - field.field.node.decision.field_project_reference - field.field.node.decision.field_related_content - field.field.node.decision.field_teaser - field.field.node.decision.field_top_images @@ -108,6 +110,8 @@ content: weight: 4 region: content hidden: + field_hide_in_timeline: true + field_project_reference: true langcode: true links: true published_at: true diff --git a/config/sync/core.entity_view_display.node.decision.hearing_ticket_add.yml b/config/sync/core.entity_view_display.node.decision.hearing_ticket_add.yml index 9406b5a6a..d4fa0e07a 100644 --- a/config/sync/core.entity_view_display.node.decision.hearing_ticket_add.yml +++ b/config/sync/core.entity_view_display.node.decision.hearing_ticket_add.yml @@ -9,7 +9,9 @@ dependencies: - field.field.node.decision.field_decision - field.field.node.decision.field_decision_date - field.field.node.decision.field_department + - field.field.node.decision.field_hide_in_timeline - field.field.node.decision.field_media_document + - field.field.node.decision.field_project_reference - field.field.node.decision.field_related_content - field.field.node.decision.field_teaser - field.field.node.decision.field_top_images @@ -53,7 +55,9 @@ hidden: field_decision: true field_decision_date: true field_department: true + field_hide_in_timeline: true field_media_document: true + field_project_reference: true field_related_content: true field_teaser: true field_top_images: true diff --git a/config/sync/core.entity_view_display.node.decision.hearing_ticket_view.yml b/config/sync/core.entity_view_display.node.decision.hearing_ticket_view.yml index b9721ce49..47dc6b301 100644 --- a/config/sync/core.entity_view_display.node.decision.hearing_ticket_view.yml +++ b/config/sync/core.entity_view_display.node.decision.hearing_ticket_view.yml @@ -9,7 +9,9 @@ dependencies: - field.field.node.decision.field_decision - field.field.node.decision.field_decision_date - field.field.node.decision.field_department + - field.field.node.decision.field_hide_in_timeline - field.field.node.decision.field_media_document + - field.field.node.decision.field_project_reference - field.field.node.decision.field_related_content - field.field.node.decision.field_teaser - field.field.node.decision.field_top_images @@ -53,7 +55,9 @@ hidden: field_decision: true field_decision_date: true field_department: true + field_hide_in_timeline: true field_media_document: true + field_project_reference: true field_related_content: true field_teaser: true field_top_images: true diff --git a/config/sync/core.entity_view_display.node.decision.list_display.yml b/config/sync/core.entity_view_display.node.decision.list_display.yml index a29c62fd4..e77615956 100644 --- a/config/sync/core.entity_view_display.node.decision.list_display.yml +++ b/config/sync/core.entity_view_display.node.decision.list_display.yml @@ -9,7 +9,9 @@ dependencies: - field.field.node.decision.field_decision - field.field.node.decision.field_decision_date - field.field.node.decision.field_department + - field.field.node.decision.field_hide_in_timeline - field.field.node.decision.field_media_document + - field.field.node.decision.field_project_reference - field.field.node.decision.field_related_content - field.field.node.decision.field_teaser - field.field.node.decision.field_top_images @@ -60,7 +62,9 @@ hidden: field_decision: true field_decision_date: true field_department: true + field_hide_in_timeline: true field_media_document: true + field_project_reference: true field_related_content: true field_top_images: true langcode: true diff --git a/config/sync/core.entity_view_display.node.decision.search_result.yml b/config/sync/core.entity_view_display.node.decision.search_result.yml index 346d1e2f2..536ca6730 100644 --- a/config/sync/core.entity_view_display.node.decision.search_result.yml +++ b/config/sync/core.entity_view_display.node.decision.search_result.yml @@ -9,7 +9,9 @@ dependencies: - field.field.node.decision.field_decision - field.field.node.decision.field_decision_date - field.field.node.decision.field_department + - field.field.node.decision.field_hide_in_timeline - field.field.node.decision.field_media_document + - field.field.node.decision.field_project_reference - field.field.node.decision.field_related_content - field.field.node.decision.field_teaser - field.field.node.decision.field_top_images @@ -100,7 +102,9 @@ content: weight: 100 region: content hidden: + field_hide_in_timeline: true field_media_document: true + field_project_reference: true field_top_images: true langcode: true published_at: true diff --git a/config/sync/core.entity_view_display.node.decision.teaser.yml b/config/sync/core.entity_view_display.node.decision.teaser.yml index f105ea5c7..69efee63e 100644 --- a/config/sync/core.entity_view_display.node.decision.teaser.yml +++ b/config/sync/core.entity_view_display.node.decision.teaser.yml @@ -9,7 +9,9 @@ dependencies: - field.field.node.decision.field_decision - field.field.node.decision.field_decision_date - field.field.node.decision.field_department + - field.field.node.decision.field_hide_in_timeline - field.field.node.decision.field_media_document + - field.field.node.decision.field_project_reference - field.field.node.decision.field_related_content - field.field.node.decision.field_teaser - field.field.node.decision.field_top_images @@ -60,7 +62,9 @@ hidden: field_decision: true field_decision_date: true field_department: true + field_hide_in_timeline: true field_media_document: true + field_project_reference: true field_related_content: true field_top_images: true langcode: true diff --git a/config/sync/core.entity_view_display.node.dialogue.citizen_proposal_add.yml b/config/sync/core.entity_view_display.node.dialogue.citizen_proposal_add.yml index 9411ab5f8..04276c571 100644 --- a/config/sync/core.entity_view_display.node.dialogue.citizen_proposal_add.yml +++ b/config/sync/core.entity_view_display.node.dialogue.citizen_proposal_add.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -40,6 +42,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true field_teaser: true field_top_images: true field_type: true diff --git a/config/sync/core.entity_view_display.node.dialogue.citizen_proposal_approval.yml b/config/sync/core.entity_view_display.node.dialogue.citizen_proposal_approval.yml index b8359ca85..8750ce132 100644 --- a/config/sync/core.entity_view_display.node.dialogue.citizen_proposal_approval.yml +++ b/config/sync/core.entity_view_display.node.dialogue.citizen_proposal_approval.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -40,6 +42,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true field_teaser: true field_top_images: true field_type: true diff --git a/config/sync/core.entity_view_display.node.dialogue.content_promoted.yml b/config/sync/core.entity_view_display.node.dialogue.content_promoted.yml index 34a08744b..618fac465 100644 --- a/config/sync/core.entity_view_display.node.dialogue.content_promoted.yml +++ b/config/sync/core.entity_view_display.node.dialogue.content_promoted.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -77,6 +79,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true langcode: true links: true published_at: true diff --git a/config/sync/core.entity_view_display.node.dialogue.default.yml b/config/sync/core.entity_view_display.node.dialogue.default.yml index 0bdcccb9f..52c19b7e5 100644 --- a/config/sync/core.entity_view_display.node.dialogue.default.yml +++ b/config/sync/core.entity_view_display.node.dialogue.default.yml @@ -10,6 +10,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -76,6 +78,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true langcode: true links: true published_at: true diff --git a/config/sync/core.entity_view_display.node.dialogue.dialogue_proposal_add.yml b/config/sync/core.entity_view_display.node.dialogue.dialogue_proposal_add.yml index e4562a8f9..2de4380d2 100644 --- a/config/sync/core.entity_view_display.node.dialogue.dialogue_proposal_add.yml +++ b/config/sync/core.entity_view_display.node.dialogue.dialogue_proposal_add.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -30,6 +32,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true field_teaser: true field_top_images: true field_type: true diff --git a/config/sync/core.entity_view_display.node.dialogue.hearing_ticket_add.yml b/config/sync/core.entity_view_display.node.dialogue.hearing_ticket_add.yml index ec5ca7aee..8528141bc 100644 --- a/config/sync/core.entity_view_display.node.dialogue.hearing_ticket_add.yml +++ b/config/sync/core.entity_view_display.node.dialogue.hearing_ticket_add.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -55,6 +57,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true field_teaser: true field_top_images: true langcode: true diff --git a/config/sync/core.entity_view_display.node.dialogue.hearing_ticket_view.yml b/config/sync/core.entity_view_display.node.dialogue.hearing_ticket_view.yml index 633adc0d7..73eefd8af 100644 --- a/config/sync/core.entity_view_display.node.dialogue.hearing_ticket_view.yml +++ b/config/sync/core.entity_view_display.node.dialogue.hearing_ticket_view.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -55,6 +57,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true field_teaser: true field_top_images: true langcode: true diff --git a/config/sync/core.entity_view_display.node.dialogue.list_display.yml b/config/sync/core.entity_view_display.node.dialogue.list_display.yml index 32b26e8dd..f4177bb96 100644 --- a/config/sync/core.entity_view_display.node.dialogue.list_display.yml +++ b/config/sync/core.entity_view_display.node.dialogue.list_display.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -52,6 +54,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true field_top_images: true langcode: true links: true diff --git a/config/sync/core.entity_view_display.node.dialogue.search_result.yml b/config/sync/core.entity_view_display.node.dialogue.search_result.yml index f229cc0a1..bd7034bb2 100644 --- a/config/sync/core.entity_view_display.node.dialogue.search_result.yml +++ b/config/sync/core.entity_view_display.node.dialogue.search_result.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -30,6 +32,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true field_teaser: true field_top_images: true field_type: true diff --git a/config/sync/core.entity_view_display.node.dialogue.teaser.yml b/config/sync/core.entity_view_display.node.dialogue.teaser.yml index 0caac0cbe..2af13e98f 100644 --- a/config/sync/core.entity_view_display.node.dialogue.teaser.yml +++ b/config/sync/core.entity_view_display.node.dialogue.teaser.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.dialogue.field_dialogue_proposal_config - field.field.node.dialogue.field_dialogue_proposal_location - field.field.node.dialogue.field_hidden_dialogue + - field.field.node.dialogue.field_hide_in_timeline + - field.field.node.dialogue.field_project_reference - field.field.node.dialogue.field_teaser - field.field.node.dialogue.field_top_images - field.field.node.dialogue.field_type @@ -62,6 +64,8 @@ hidden: field_dialogue_proposal_config: true field_dialogue_proposal_location: true field_hidden_dialogue: true + field_hide_in_timeline: true + field_project_reference: true field_top_images: true langcode: true published_at: true diff --git a/config/sync/core.entity_view_display.node.hearing.default.yml b/config/sync/core.entity_view_display.node.hearing.default.yml index c0f2fc058..857b89a8c 100644 --- a/config/sync/core.entity_view_display.node.hearing.default.yml +++ b/config/sync/core.entity_view_display.node.hearing.default.yml @@ -15,12 +15,14 @@ dependencies: - field.field.node.hearing.field_getorganized_case_id - field.field.node.hearing.field_hearing_ticket_add - field.field.node.hearing.field_hearing_ticket_list + - field.field.node.hearing.field_hide_in_timeline - field.field.node.hearing.field_lokalplaner - field.field.node.hearing.field_map - field.field.node.hearing.field_map_display - field.field.node.hearing.field_media_document - field.field.node.hearing.field_media_image - field.field.node.hearing.field_more_info + - field.field.node.hearing.field_project_reference - field.field.node.hearing.field_reply_deadline - field.field.node.hearing.field_start_date - field.field.node.hearing.field_tags @@ -57,6 +59,7 @@ third_party_settings: header: - 'dynamic_block_field:node-hearing_warning' - node_title + - field_hide_in_timeline left: - field_teaser - field_description @@ -81,7 +84,7 @@ third_party_settings: fields: 'dynamic_block_field:node-hearing_tickets': plugin_id: 'dynamic_block_field:node-hearing_tickets' - weight: 7 + weight: 6 label: hidden formatter: default 'dynamic_block_field:node-hearing_warning': @@ -91,22 +94,22 @@ third_party_settings: formatter: default 'dynamic_block_field:node-nearest_hearings': plugin_id: 'dynamic_block_field:node-nearest_hearings' - weight: 21 + weight: 19 label: above formatter: default 'dynamic_block_field:node-quicklinks': plugin_id: 'dynamic_block_field:node-quicklinks' - weight: 8 + weight: 7 label: above formatter: default 'dynamic_token_field:node-header_information': plugin_id: 'dynamic_token_field:node-header_information' - weight: 9 + weight: 8 label: hidden formatter: default 'dynamic_token_field:node-node_id_hearing': plugin_id: 'dynamic_token_field:node-node_id_hearing' - weight: 15 + weight: 14 label: above formatter: default node_title: @@ -129,14 +132,14 @@ content: settings: link: false third_party_settings: { } - weight: 14 + weight: 13 region: right field_contact: type: text_default label: above settings: { } third_party_settings: { } - weight: 16 + weight: 15 region: right field_description: type: text_default @@ -145,19 +148,29 @@ content: third_party_settings: { } weight: 3 region: left + field_hide_in_timeline: + type: boolean + label: above + settings: + format: default + format_custom_false: '' + format_custom_true: '' + third_party_settings: { } + weight: 21 + region: header field_lokalplaner: type: hoeringsportal_data_localplan_default label: above settings: { } third_party_settings: { } - weight: 13 + weight: 12 region: right field_map: type: hoeringsportal_data_map_default label: hidden settings: { } third_party_settings: { } - weight: 20 + weight: 18 region: footer field_media_document: type: entity_reference_entity_view @@ -186,7 +199,7 @@ content: label: above settings: { } third_party_settings: { } - weight: 17 + weight: 16 region: right field_reply_deadline: type: datetime_default @@ -195,7 +208,7 @@ content: timezone_override: '' format_type: hoeringsportal_datetime third_party_settings: { } - weight: 11 + weight: 10 region: right field_start_date: type: datetime_default @@ -204,7 +217,7 @@ content: timezone_override: '' format_type: hoeringsportal_date third_party_settings: { } - weight: 10 + weight: 9 region: right field_teaser: type: basic_string @@ -219,12 +232,12 @@ content: settings: link: false third_party_settings: { } - weight: 12 + weight: 11 region: right sharing_buttons: settings: { } third_party_settings: { } - weight: 18 + weight: 17 region: right hidden: field_content_state: true @@ -237,6 +250,7 @@ hidden: field_hearing_ticket_add: true field_hearing_ticket_list: true field_map_display: true + field_project_reference: true field_tags: true langcode: true links: true diff --git a/config/sync/core.entity_view_display.node.hearing.hearing_ticket_add.yml b/config/sync/core.entity_view_display.node.hearing.hearing_ticket_add.yml index becf36efd..f0c774bcd 100644 --- a/config/sync/core.entity_view_display.node.hearing.hearing_ticket_add.yml +++ b/config/sync/core.entity_view_display.node.hearing.hearing_ticket_add.yml @@ -16,12 +16,14 @@ dependencies: - field.field.node.hearing.field_getorganized_case_id - field.field.node.hearing.field_hearing_ticket_add - field.field.node.hearing.field_hearing_ticket_list + - field.field.node.hearing.field_hide_in_timeline - field.field.node.hearing.field_lokalplaner - field.field.node.hearing.field_map - field.field.node.hearing.field_map_display - field.field.node.hearing.field_media_document - field.field.node.hearing.field_media_image - field.field.node.hearing.field_more_info + - field.field.node.hearing.field_project_reference - field.field.node.hearing.field_reply_deadline - field.field.node.hearing.field_start_date - field.field.node.hearing.field_tags @@ -172,11 +174,13 @@ hidden: field_getorganized_case_id: true field_hearing_ticket_add: true field_hearing_ticket_list: true + field_hide_in_timeline: true field_lokalplaner: true field_map: true field_map_display: true field_media_document: true field_media_image: true + field_project_reference: true field_teaser: true langcode: true links: true diff --git a/config/sync/core.entity_view_display.node.hearing.hearing_ticket_view.yml b/config/sync/core.entity_view_display.node.hearing.hearing_ticket_view.yml index f1fc74c6e..cd476f97c 100644 --- a/config/sync/core.entity_view_display.node.hearing.hearing_ticket_view.yml +++ b/config/sync/core.entity_view_display.node.hearing.hearing_ticket_view.yml @@ -16,12 +16,14 @@ dependencies: - field.field.node.hearing.field_getorganized_case_id - field.field.node.hearing.field_hearing_ticket_add - field.field.node.hearing.field_hearing_ticket_list + - field.field.node.hearing.field_hide_in_timeline - field.field.node.hearing.field_lokalplaner - field.field.node.hearing.field_map - field.field.node.hearing.field_map_display - field.field.node.hearing.field_media_document - field.field.node.hearing.field_media_image - field.field.node.hearing.field_more_info + - field.field.node.hearing.field_project_reference - field.field.node.hearing.field_reply_deadline - field.field.node.hearing.field_start_date - field.field.node.hearing.field_tags @@ -184,11 +186,13 @@ hidden: field_getorganized_case_id: true field_hearing_ticket_add: true field_hearing_ticket_list: true + field_hide_in_timeline: true field_lokalplaner: true field_map: true field_map_display: true field_media_document: true field_media_image: true + field_project_reference: true field_teaser: true langcode: true links: true diff --git a/config/sync/core.entity_view_display.node.hearing.list_display.yml b/config/sync/core.entity_view_display.node.hearing.list_display.yml index 1b57050e4..db9076d66 100644 --- a/config/sync/core.entity_view_display.node.hearing.list_display.yml +++ b/config/sync/core.entity_view_display.node.hearing.list_display.yml @@ -16,12 +16,14 @@ dependencies: - field.field.node.hearing.field_getorganized_case_id - field.field.node.hearing.field_hearing_ticket_add - field.field.node.hearing.field_hearing_ticket_list + - field.field.node.hearing.field_hide_in_timeline - field.field.node.hearing.field_lokalplaner - field.field.node.hearing.field_map - field.field.node.hearing.field_map_display - field.field.node.hearing.field_media_document - field.field.node.hearing.field_media_image - field.field.node.hearing.field_more_info + - field.field.node.hearing.field_project_reference - field.field.node.hearing.field_reply_deadline - field.field.node.hearing.field_start_date - field.field.node.hearing.field_tags @@ -88,11 +90,13 @@ hidden: field_getorganized_case_id: true field_hearing_ticket_add: true field_hearing_ticket_list: true + field_hide_in_timeline: true field_lokalplaner: true field_map: true field_map_display: true field_media_document: true field_more_info: true + field_project_reference: true field_reply_deadline: true field_start_date: true field_tags: true diff --git a/config/sync/core.entity_view_display.node.hearing.search_result.yml b/config/sync/core.entity_view_display.node.hearing.search_result.yml index 0f1bfe5be..a331ceec8 100644 --- a/config/sync/core.entity_view_display.node.hearing.search_result.yml +++ b/config/sync/core.entity_view_display.node.hearing.search_result.yml @@ -16,12 +16,14 @@ dependencies: - field.field.node.hearing.field_getorganized_case_id - field.field.node.hearing.field_hearing_ticket_add - field.field.node.hearing.field_hearing_ticket_list + - field.field.node.hearing.field_hide_in_timeline - field.field.node.hearing.field_lokalplaner - field.field.node.hearing.field_map - field.field.node.hearing.field_map_display - field.field.node.hearing.field_media_document - field.field.node.hearing.field_media_image - field.field.node.hearing.field_more_info + - field.field.node.hearing.field_project_reference - field.field.node.hearing.field_reply_deadline - field.field.node.hearing.field_start_date - field.field.node.hearing.field_tags @@ -77,12 +79,14 @@ hidden: field_getorganized_case_id: true field_hearing_ticket_add: true field_hearing_ticket_list: true + field_hide_in_timeline: true field_lokalplaner: true field_map: true field_map_display: true field_media_document: true field_media_image: true field_more_info: true + field_project_reference: true field_reply_deadline: true field_start_date: true field_tags: true diff --git a/config/sync/core.entity_view_display.node.hearing.teaser.yml b/config/sync/core.entity_view_display.node.hearing.teaser.yml index 9384ef5f0..65e141e21 100644 --- a/config/sync/core.entity_view_display.node.hearing.teaser.yml +++ b/config/sync/core.entity_view_display.node.hearing.teaser.yml @@ -16,12 +16,14 @@ dependencies: - field.field.node.hearing.field_getorganized_case_id - field.field.node.hearing.field_hearing_ticket_add - field.field.node.hearing.field_hearing_ticket_list + - field.field.node.hearing.field_hide_in_timeline - field.field.node.hearing.field_lokalplaner - field.field.node.hearing.field_map - field.field.node.hearing.field_map_display - field.field.node.hearing.field_media_document - field.field.node.hearing.field_media_image - field.field.node.hearing.field_more_info + - field.field.node.hearing.field_project_reference - field.field.node.hearing.field_reply_deadline - field.field.node.hearing.field_start_date - field.field.node.hearing.field_tags @@ -108,11 +110,13 @@ hidden: field_getorganized_case_id: true field_hearing_ticket_add: true field_hearing_ticket_list: true + field_hide_in_timeline: true field_lokalplaner: true field_map: true field_map_display: true field_media_document: true field_more_info: true + field_project_reference: true field_tags: true langcode: true links: true diff --git a/config/sync/core.entity_view_display.node.project_main_page.content_promoted.yml b/config/sync/core.entity_view_display.node.project_main_page.content_promoted.yml index a6a95f6c3..cfe4b188b 100644 --- a/config/sync/core.entity_view_display.node.project_main_page.content_promoted.yml +++ b/config/sync/core.entity_view_display.node.project_main_page.content_promoted.yml @@ -9,7 +9,10 @@ dependencies: - field.field.node.project_main_page.field_department - field.field.node.project_main_page.field_project_category - field.field.node.project_main_page.field_project_image + - field.field.node.project_main_page.field_project_status - field.field.node.project_main_page.field_short_description + - field.field.node.project_main_page.field_show_timeline + - field.field.node.project_main_page.field_timeline - field.field.node.project_main_page.field_type - node.type.project_main_page module: @@ -52,6 +55,8 @@ hidden: field_project_image: true field_project_status: true field_short_description: true + field_show_timeline: true + field_timeline: true langcode: true published_at: true search_api_excerpt: true diff --git a/config/sync/core.entity_view_display.node.project_main_page.default.yml b/config/sync/core.entity_view_display.node.project_main_page.default.yml index fcb4c8b2e..925a24b47 100644 --- a/config/sync/core.entity_view_display.node.project_main_page.default.yml +++ b/config/sync/core.entity_view_display.node.project_main_page.default.yml @@ -10,6 +10,8 @@ dependencies: - field.field.node.project_main_page.field_project_image - field.field.node.project_main_page.field_project_status - field.field.node.project_main_page.field_short_description + - field.field.node.project_main_page.field_show_timeline + - field.field.node.project_main_page.field_timeline - field.field.node.project_main_page.field_type - node.type.project_main_page module: @@ -78,6 +80,25 @@ content: third_party_settings: { } weight: 2 region: content + field_show_timeline: + type: boolean + label: above + settings: + format: default + format_custom_false: '' + format_custom_true: '' + third_party_settings: { } + weight: 7 + region: content + field_timeline: + type: entity_reference_revisions_entity_view + label: above + settings: + view_mode: default + link: '' + third_party_settings: { } + weight: 8 + region: content field_type: type: entity_reference_label label: hidden diff --git a/config/sync/core.entity_view_display.node.project_main_page.hearing_ticket_add.yml b/config/sync/core.entity_view_display.node.project_main_page.hearing_ticket_add.yml index 79506cd87..439f34568 100644 --- a/config/sync/core.entity_view_display.node.project_main_page.hearing_ticket_add.yml +++ b/config/sync/core.entity_view_display.node.project_main_page.hearing_ticket_add.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.project_main_page.field_project_image - field.field.node.project_main_page.field_project_status - field.field.node.project_main_page.field_short_description + - field.field.node.project_main_page.field_show_timeline + - field.field.node.project_main_page.field_timeline - field.field.node.project_main_page.field_type - node.type.project_main_page module: @@ -53,6 +55,8 @@ hidden: field_project_image: true field_project_status: true field_short_description: true + field_show_timeline: true + field_timeline: true langcode: true published_at: true search_api_excerpt: true diff --git a/config/sync/core.entity_view_display.node.project_main_page.hearing_ticket_view.yml b/config/sync/core.entity_view_display.node.project_main_page.hearing_ticket_view.yml index 839c10726..9ff43e715 100644 --- a/config/sync/core.entity_view_display.node.project_main_page.hearing_ticket_view.yml +++ b/config/sync/core.entity_view_display.node.project_main_page.hearing_ticket_view.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.project_main_page.field_project_image - field.field.node.project_main_page.field_project_status - field.field.node.project_main_page.field_short_description + - field.field.node.project_main_page.field_show_timeline + - field.field.node.project_main_page.field_timeline - field.field.node.project_main_page.field_type - node.type.project_main_page module: @@ -53,6 +55,8 @@ hidden: field_project_image: true field_project_status: true field_short_description: true + field_show_timeline: true + field_timeline: true langcode: true published_at: true search_api_excerpt: true diff --git a/config/sync/core.entity_view_display.node.project_main_page.list_display.yml b/config/sync/core.entity_view_display.node.project_main_page.list_display.yml index 5f76898fa..6a02e04a1 100644 --- a/config/sync/core.entity_view_display.node.project_main_page.list_display.yml +++ b/config/sync/core.entity_view_display.node.project_main_page.list_display.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.project_main_page.field_project_image - field.field.node.project_main_page.field_project_status - field.field.node.project_main_page.field_short_description + - field.field.node.project_main_page.field_show_timeline + - field.field.node.project_main_page.field_timeline - field.field.node.project_main_page.field_type - node.type.project_main_page module: @@ -64,6 +66,8 @@ hidden: field_content_sections: true field_department: true field_project_status: true + field_show_timeline: true + field_timeline: true langcode: true links: true published_at: true diff --git a/config/sync/core.entity_view_display.node.project_main_page.teaser.yml b/config/sync/core.entity_view_display.node.project_main_page.teaser.yml index f7b87b98c..b02789777 100644 --- a/config/sync/core.entity_view_display.node.project_main_page.teaser.yml +++ b/config/sync/core.entity_view_display.node.project_main_page.teaser.yml @@ -11,6 +11,8 @@ dependencies: - field.field.node.project_main_page.field_project_image - field.field.node.project_main_page.field_project_status - field.field.node.project_main_page.field_short_description + - field.field.node.project_main_page.field_show_timeline + - field.field.node.project_main_page.field_timeline - field.field.node.project_main_page.field_type - node.type.project_main_page module: @@ -65,6 +67,8 @@ hidden: field_content_sections: true field_department: true field_project_status: true + field_show_timeline: true + field_timeline: true langcode: true links: true published_at: true diff --git a/config/sync/core.entity_view_display.node.public_meeting.default.yml b/config/sync/core.entity_view_display.node.public_meeting.default.yml index 8c95924c2..7819e906a 100644 --- a/config/sync/core.entity_view_display.node.public_meeting.default.yml +++ b/config/sync/core.entity_view_display.node.public_meeting.default.yml @@ -16,6 +16,7 @@ dependencies: - field.field.node.public_meeting.field_email_address - field.field.node.public_meeting.field_first_meeting_time - field.field.node.public_meeting.field_hidden_signup + - field.field.node.public_meeting.field_hide_in_timeline - field.field.node.public_meeting.field_last_meeting_time - field.field.node.public_meeting.field_last_meeting_time_end - field.field.node.public_meeting.field_map @@ -23,6 +24,7 @@ dependencies: - field.field.node.public_meeting.field_media_image_single - field.field.node.public_meeting.field_pretix_dates - field.field.node.public_meeting.field_pretix_event_settings + - field.field.node.public_meeting.field_project_reference - field.field.node.public_meeting.field_public_meeting_cancelled - field.field.node.public_meeting.field_registration_deadline - field.field.node.public_meeting.field_section @@ -50,7 +52,7 @@ third_party_settings: label: Tilmelding parent_name: '' region: hidden - weight: 33 + weight: 35 format_type: html_element format_settings: classes: '' @@ -75,7 +77,7 @@ content: settings: link_to_entity: false third_party_settings: { } - weight: 7 + weight: 8 region: content field_activity_type: type: entity_reference_label @@ -83,7 +85,7 @@ content: settings: link: false third_party_settings: { } - weight: 14 + weight: 16 region: content field_address: type: string @@ -123,14 +125,14 @@ content: format_custom_false: '' format_custom_true: '' third_party_settings: { } - weight: 6 + weight: 7 region: content field_map: type: hoeringsportal_data_map_default label: hidden settings: { } third_party_settings: { } - weight: 11 + weight: 13 region: content field_media_document: type: entity_reference_entity_view @@ -148,7 +150,7 @@ content: label: hidden settings: { } third_party_settings: { } - weight: 8 + weight: 10 region: content field_public_meeting_cancelled: type: boolean @@ -166,7 +168,7 @@ content: settings: timezone_override: '' third_party_settings: { } - weight: 9 + weight: 11 region: content field_section: type: entity_reference_revisions_entity_view @@ -175,7 +177,7 @@ content: view_mode: default link: '' third_party_settings: { } - weight: 10 + weight: 12 region: content field_signup_link: type: link @@ -187,21 +189,21 @@ content: rel: '0' target: _blank third_party_settings: { } - weight: 12 + weight: 14 region: content field_signup_selection: type: list_key label: hidden settings: { } third_party_settings: { } - weight: 7 + weight: 9 region: content field_signup_text: type: text_default label: hidden settings: { } third_party_settings: { } - weight: 13 + weight: 15 region: content field_teaser: type: basic_string @@ -218,10 +220,12 @@ hidden: field_deskpro_department_id: true field_email_address: true field_first_meeting_time: true + field_hide_in_timeline: true field_last_meeting_time: true field_last_meeting_time_end: true field_media_image_single: true field_pretix_event_settings: true + field_project_reference: true field_type: true langcode: true links: true diff --git a/config/sync/core.entity_view_display.node.public_meeting.full.yml b/config/sync/core.entity_view_display.node.public_meeting.full.yml index 88eb8913a..357d42ba7 100644 --- a/config/sync/core.entity_view_display.node.public_meeting.full.yml +++ b/config/sync/core.entity_view_display.node.public_meeting.full.yml @@ -17,6 +17,7 @@ dependencies: - field.field.node.public_meeting.field_email_address - field.field.node.public_meeting.field_first_meeting_time - field.field.node.public_meeting.field_hidden_signup + - field.field.node.public_meeting.field_hide_in_timeline - field.field.node.public_meeting.field_last_meeting_time - field.field.node.public_meeting.field_last_meeting_time_end - field.field.node.public_meeting.field_map @@ -24,6 +25,7 @@ dependencies: - field.field.node.public_meeting.field_media_image_single - field.field.node.public_meeting.field_pretix_dates - field.field.node.public_meeting.field_pretix_event_settings + - field.field.node.public_meeting.field_project_reference - field.field.node.public_meeting.field_public_meeting_cancelled - field.field.node.public_meeting.field_registration_deadline - field.field.node.public_meeting.field_section @@ -144,10 +146,12 @@ hidden: field_department: true field_first_meeting_time: true field_hidden_signup: true + field_hide_in_timeline: true field_last_meeting_time: true field_last_meeting_time_end: true field_media_image_single: true field_pretix_event_settings: true + field_project_reference: true field_public_meeting_cancelled: true field_registration_deadline: true field_signup_link: true diff --git a/config/sync/core.entity_view_display.node.public_meeting.hearing_ticket_add.yml b/config/sync/core.entity_view_display.node.public_meeting.hearing_ticket_add.yml index 28d37b58f..dc3c96624 100644 --- a/config/sync/core.entity_view_display.node.public_meeting.hearing_ticket_add.yml +++ b/config/sync/core.entity_view_display.node.public_meeting.hearing_ticket_add.yml @@ -17,6 +17,7 @@ dependencies: - field.field.node.public_meeting.field_email_address - field.field.node.public_meeting.field_first_meeting_time - field.field.node.public_meeting.field_hidden_signup + - field.field.node.public_meeting.field_hide_in_timeline - field.field.node.public_meeting.field_last_meeting_time - field.field.node.public_meeting.field_last_meeting_time_end - field.field.node.public_meeting.field_map @@ -24,6 +25,7 @@ dependencies: - field.field.node.public_meeting.field_media_image_single - field.field.node.public_meeting.field_pretix_dates - field.field.node.public_meeting.field_pretix_event_settings + - field.field.node.public_meeting.field_project_reference - field.field.node.public_meeting.field_public_meeting_cancelled - field.field.node.public_meeting.field_registration_deadline - field.field.node.public_meeting.field_section @@ -165,11 +167,13 @@ hidden: field_email_address: true field_first_meeting_time: true field_hidden_signup: true + field_hide_in_timeline: true field_last_meeting_time: true field_last_meeting_time_end: true field_map: true field_pretix_dates: true field_pretix_event_settings: true + field_project_reference: true field_public_meeting_cancelled: true field_signup_link: true field_signup_selection: true diff --git a/config/sync/core.entity_view_display.node.public_meeting.hearing_ticket_view.yml b/config/sync/core.entity_view_display.node.public_meeting.hearing_ticket_view.yml index cb6b9f4e0..c9b458035 100644 --- a/config/sync/core.entity_view_display.node.public_meeting.hearing_ticket_view.yml +++ b/config/sync/core.entity_view_display.node.public_meeting.hearing_ticket_view.yml @@ -17,6 +17,7 @@ dependencies: - field.field.node.public_meeting.field_email_address - field.field.node.public_meeting.field_first_meeting_time - field.field.node.public_meeting.field_hidden_signup + - field.field.node.public_meeting.field_hide_in_timeline - field.field.node.public_meeting.field_last_meeting_time - field.field.node.public_meeting.field_last_meeting_time_end - field.field.node.public_meeting.field_map @@ -24,6 +25,7 @@ dependencies: - field.field.node.public_meeting.field_media_image_single - field.field.node.public_meeting.field_pretix_dates - field.field.node.public_meeting.field_pretix_event_settings + - field.field.node.public_meeting.field_project_reference - field.field.node.public_meeting.field_public_meeting_cancelled - field.field.node.public_meeting.field_registration_deadline - field.field.node.public_meeting.field_section @@ -165,11 +167,13 @@ hidden: field_email_address: true field_first_meeting_time: true field_hidden_signup: true + field_hide_in_timeline: true field_last_meeting_time: true field_last_meeting_time_end: true field_map: true field_pretix_dates: true field_pretix_event_settings: true + field_project_reference: true field_public_meeting_cancelled: true field_signup_link: true field_signup_selection: true diff --git a/config/sync/core.entity_view_display.node.public_meeting.list_display.yml b/config/sync/core.entity_view_display.node.public_meeting.list_display.yml index 173588bdc..5a3c5cded 100644 --- a/config/sync/core.entity_view_display.node.public_meeting.list_display.yml +++ b/config/sync/core.entity_view_display.node.public_meeting.list_display.yml @@ -17,6 +17,7 @@ dependencies: - field.field.node.public_meeting.field_email_address - field.field.node.public_meeting.field_first_meeting_time - field.field.node.public_meeting.field_hidden_signup + - field.field.node.public_meeting.field_hide_in_timeline - field.field.node.public_meeting.field_last_meeting_time - field.field.node.public_meeting.field_last_meeting_time_end - field.field.node.public_meeting.field_map @@ -24,6 +25,7 @@ dependencies: - field.field.node.public_meeting.field_media_image_single - field.field.node.public_meeting.field_pretix_dates - field.field.node.public_meeting.field_pretix_event_settings + - field.field.node.public_meeting.field_project_reference - field.field.node.public_meeting.field_public_meeting_cancelled - field.field.node.public_meeting.field_registration_deadline - field.field.node.public_meeting.field_section @@ -87,12 +89,14 @@ hidden: field_email_address: true field_first_meeting_time: true field_hidden_signup: true + field_hide_in_timeline: true field_last_meeting_time: true field_last_meeting_time_end: true field_map: true field_media_document: true field_pretix_dates: true field_pretix_event_settings: true + field_project_reference: true field_public_meeting_cancelled: true field_registration_deadline: true field_section: true diff --git a/config/sync/core.entity_view_display.node.public_meeting.search_result.yml b/config/sync/core.entity_view_display.node.public_meeting.search_result.yml index 0ea634599..231657f8a 100644 --- a/config/sync/core.entity_view_display.node.public_meeting.search_result.yml +++ b/config/sync/core.entity_view_display.node.public_meeting.search_result.yml @@ -17,6 +17,7 @@ dependencies: - field.field.node.public_meeting.field_email_address - field.field.node.public_meeting.field_first_meeting_time - field.field.node.public_meeting.field_hidden_signup + - field.field.node.public_meeting.field_hide_in_timeline - field.field.node.public_meeting.field_last_meeting_time - field.field.node.public_meeting.field_last_meeting_time_end - field.field.node.public_meeting.field_map @@ -24,6 +25,7 @@ dependencies: - field.field.node.public_meeting.field_media_image_single - field.field.node.public_meeting.field_pretix_dates - field.field.node.public_meeting.field_pretix_event_settings + - field.field.node.public_meeting.field_project_reference - field.field.node.public_meeting.field_public_meeting_cancelled - field.field.node.public_meeting.field_registration_deadline - field.field.node.public_meeting.field_section @@ -108,6 +110,7 @@ hidden: field_email_address: true field_first_meeting_time: true field_hidden_signup: true + field_hide_in_timeline: true field_last_meeting_time: true field_last_meeting_time_end: true field_map: true @@ -115,6 +118,7 @@ hidden: field_media_image_single: true field_pretix_dates: true field_pretix_event_settings: true + field_project_reference: true field_public_meeting_cancelled: true field_registration_deadline: true field_section: true diff --git a/config/sync/core.entity_view_display.node.public_meeting.teaser.yml b/config/sync/core.entity_view_display.node.public_meeting.teaser.yml index 0d6d8f57f..9baea2023 100644 --- a/config/sync/core.entity_view_display.node.public_meeting.teaser.yml +++ b/config/sync/core.entity_view_display.node.public_meeting.teaser.yml @@ -17,6 +17,7 @@ dependencies: - field.field.node.public_meeting.field_email_address - field.field.node.public_meeting.field_first_meeting_time - field.field.node.public_meeting.field_hidden_signup + - field.field.node.public_meeting.field_hide_in_timeline - field.field.node.public_meeting.field_last_meeting_time - field.field.node.public_meeting.field_last_meeting_time_end - field.field.node.public_meeting.field_map @@ -24,6 +25,7 @@ dependencies: - field.field.node.public_meeting.field_media_image_single - field.field.node.public_meeting.field_pretix_dates - field.field.node.public_meeting.field_pretix_event_settings + - field.field.node.public_meeting.field_project_reference - field.field.node.public_meeting.field_public_meeting_cancelled - field.field.node.public_meeting.field_registration_deadline - field.field.node.public_meeting.field_section @@ -146,10 +148,12 @@ hidden: field_description: true field_email_address: true field_hidden_signup: true + field_hide_in_timeline: true field_last_meeting_time_end: true field_map: true field_media_document: true field_pretix_event_settings: true + field_project_reference: true field_section: true field_signup_link: true field_signup_selection: true diff --git a/config/sync/core.entity_view_display.paragraph.timeline_note.default.yml b/config/sync/core.entity_view_display.paragraph.timeline_note.default.yml new file mode 100644 index 000000000..e96cc64d6 --- /dev/null +++ b/config/sync/core.entity_view_display.paragraph.timeline_note.default.yml @@ -0,0 +1,75 @@ +uuid: 1ceb7913-18b2-4203-9b21-28e4eb226643 +langcode: da +status: true +dependencies: + config: + - field.field.paragraph.timeline_note.field_date + - field.field.paragraph.timeline_note.field_external_link + - field.field.paragraph.timeline_note.field_note + - field.field.paragraph.timeline_note.field_paragraph_image + - field.field.paragraph.timeline_note.field_subtitle + - field.field.paragraph.timeline_note.field_title + - paragraphs.paragraphs_type.timeline_note + module: + - datetime + - link +id: paragraph.timeline_note.default +targetEntityType: paragraph +bundle: timeline_note +mode: default +content: + field_date: + type: datetime_default + label: hidden + settings: + timezone_override: '' + format_type: medium + third_party_settings: { } + weight: 3 + region: content + field_external_link: + type: link + label: hidden + settings: + trim_length: 80 + url_only: false + url_plain: false + rel: '' + target: '' + third_party_settings: { } + weight: 1 + region: content + field_note: + type: basic_string + label: hidden + settings: { } + third_party_settings: { } + weight: 4 + region: content + field_paragraph_image: + type: entity_reference_entity_view + label: hidden + settings: + view_mode: teaser_display + link: false + third_party_settings: { } + weight: 2 + region: content + field_subtitle: + type: string + label: hidden + settings: + link_to_entity: false + third_party_settings: { } + weight: 0 + region: content + field_title: + type: string + label: hidden + settings: + link_to_entity: false + third_party_settings: { } + weight: 2 + region: content +hidden: + search_api_excerpt: true diff --git a/config/sync/core.extension.yml b/config/sync/core.extension.yml index 4bff7ca9f..380240aba 100644 --- a/config/sync/core.extension.yml +++ b/config/sync/core.extension.yml @@ -59,6 +59,7 @@ module: hoeringsportal_forms: 0 hoeringsportal_hearing: 0 hoeringsportal_openid_connect: 0 + hoeringsportal_project: 0 hoeringsportal_public_meeting: 0 hoeringsportal_quicklinks: 0 honeypot: 0 diff --git a/config/sync/field.field.node.course.field_hide_in_timeline.yml b/config/sync/field.field.node.course.field_hide_in_timeline.yml new file mode 100644 index 000000000..ae58e5aff --- /dev/null +++ b/config/sync/field.field.node.course.field_hide_in_timeline.yml @@ -0,0 +1,21 @@ +uuid: e6de0c88-cc86-430c-b89d-e4e5aacf99d0 +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_hide_in_timeline + - node.type.course +id: node.course.field_hide_in_timeline +field_name: field_hide_in_timeline +entity_type: node +bundle: course +label: 'Skjul på tidslinje' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + on_label: 'Hide in timeline' + off_label: 'Show in timeline' +field_type: boolean diff --git a/config/sync/field.field.node.course.field_project_reference.yml b/config/sync/field.field.node.course.field_project_reference.yml new file mode 100644 index 000000000..ddbcbb1e5 --- /dev/null +++ b/config/sync/field.field.node.course.field_project_reference.yml @@ -0,0 +1,29 @@ +uuid: bbc97c1a-412a-4e2d-9fc3-23ada6250fb8 +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_project_reference + - node.type.course + - node.type.project_main_page +id: node.course.field_project_reference +field_name: field_project_reference +entity_type: node +bundle: course +label: 'Projektreference' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:node' + handler_settings: + target_bundles: + project_main_page: project_main_page + sort: + field: _none + direction: ASC + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/config/sync/field.field.node.decision.field_hide_in_timeline.yml b/config/sync/field.field.node.decision.field_hide_in_timeline.yml new file mode 100644 index 000000000..66b14b9d9 --- /dev/null +++ b/config/sync/field.field.node.decision.field_hide_in_timeline.yml @@ -0,0 +1,21 @@ +uuid: 22980a95-170a-4128-bcb7-24cdc542ccfb +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_hide_in_timeline + - node.type.decision +id: node.decision.field_hide_in_timeline +field_name: field_hide_in_timeline +entity_type: node +bundle: decision +label: 'Skjul på tidslinje' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + on_label: 'Hide in timeline' + off_label: 'Show in timeline' +field_type: boolean diff --git a/config/sync/field.field.node.decision.field_project_reference.yml b/config/sync/field.field.node.decision.field_project_reference.yml new file mode 100644 index 000000000..59f7d22f2 --- /dev/null +++ b/config/sync/field.field.node.decision.field_project_reference.yml @@ -0,0 +1,29 @@ +uuid: 7e2d6971-4de0-4479-9c2b-fad24cfd0f72 +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_project_reference + - node.type.decision + - node.type.project_main_page +id: node.decision.field_project_reference +field_name: field_project_reference +entity_type: node +bundle: decision +label: 'Projekt reference' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:node' + handler_settings: + target_bundles: + project_main_page: project_main_page + sort: + field: _none + direction: ASC + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/config/sync/field.field.node.dialogue.field_hide_in_timeline.yml b/config/sync/field.field.node.dialogue.field_hide_in_timeline.yml new file mode 100644 index 000000000..d67143631 --- /dev/null +++ b/config/sync/field.field.node.dialogue.field_hide_in_timeline.yml @@ -0,0 +1,21 @@ +uuid: 75bf28b8-e2b2-4a15-8ae5-f23444a363a5 +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_hide_in_timeline + - node.type.dialogue +id: node.dialogue.field_hide_in_timeline +field_name: field_hide_in_timeline +entity_type: node +bundle: dialogue +label: 'Skjul på tidslinje' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + on_label: 'Hide in timeline' + off_label: 'Show in timeline' +field_type: boolean diff --git a/config/sync/field.field.node.dialogue.field_project_reference.yml b/config/sync/field.field.node.dialogue.field_project_reference.yml new file mode 100644 index 000000000..a5b99fdb0 --- /dev/null +++ b/config/sync/field.field.node.dialogue.field_project_reference.yml @@ -0,0 +1,29 @@ +uuid: 9adecae3-61e4-42b8-a8a4-ef513cdf347c +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_project_reference + - node.type.dialogue + - node.type.project_main_page +id: node.dialogue.field_project_reference +field_name: field_project_reference +entity_type: node +bundle: dialogue +label: 'Projekt reference' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:node' + handler_settings: + target_bundles: + project_main_page: project_main_page + sort: + field: _none + direction: ASC + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/config/sync/field.field.node.hearing.field_hide_in_timeline.yml b/config/sync/field.field.node.hearing.field_hide_in_timeline.yml new file mode 100644 index 000000000..d6c18c4d9 --- /dev/null +++ b/config/sync/field.field.node.hearing.field_hide_in_timeline.yml @@ -0,0 +1,21 @@ +uuid: 98e146a6-3005-496a-9245-0f72987ea58d +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_hide_in_timeline + - node.type.hearing +id: node.hearing.field_hide_in_timeline +field_name: field_hide_in_timeline +entity_type: node +bundle: hearing +label: 'Skjul på tidslinje' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + on_label: 'Hide in timeline' + off_label: 'Show in timeline' +field_type: boolean diff --git a/config/sync/field.field.node.hearing.field_project_reference.yml b/config/sync/field.field.node.hearing.field_project_reference.yml new file mode 100644 index 000000000..b98096744 --- /dev/null +++ b/config/sync/field.field.node.hearing.field_project_reference.yml @@ -0,0 +1,29 @@ +uuid: 0a231d25-ed97-440d-903a-37b162e64437 +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_project_reference + - node.type.hearing + - node.type.project_main_page +id: node.hearing.field_project_reference +field_name: field_project_reference +entity_type: node +bundle: hearing +label: 'Projekt reference' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:node' + handler_settings: + target_bundles: + project_main_page: project_main_page + sort: + field: _none + direction: ASC + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/config/sync/field.field.node.project_main_page.field_show_timeline.yml b/config/sync/field.field.node.project_main_page.field_show_timeline.yml new file mode 100644 index 000000000..cf25f6b7b --- /dev/null +++ b/config/sync/field.field.node.project_main_page.field_show_timeline.yml @@ -0,0 +1,21 @@ +uuid: 2cdaee17-181e-43b3-9941-521eb68555d9 +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_show_timeline + - node.type.project_main_page +id: node.project_main_page.field_show_timeline +field_name: field_show_timeline +entity_type: node +bundle: project_main_page +label: 'Brug tidslinje' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + on_label: 'Brug tidslinje' + off_label: 'Brug ikke tidslinje' +field_type: boolean diff --git a/config/sync/field.field.node.project_main_page.field_timeline.yml b/config/sync/field.field.node.project_main_page.field_timeline.yml new file mode 100644 index 000000000..ce98dd004 --- /dev/null +++ b/config/sync/field.field.node.project_main_page.field_timeline.yml @@ -0,0 +1,82 @@ +uuid: b79c95ae-6255-4eb8-a0c4-a08324e38fee +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_timeline + - node.type.project_main_page + - paragraphs.paragraphs_type.timeline_note + module: + - entity_reference_revisions +id: node.project_main_page.field_timeline +field_name: field_timeline +entity_type: node +bundle: project_main_page +label: Tidslinje +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:paragraph' + handler_settings: + target_bundles: + timeline_note: timeline_note + negate: 0 + target_bundles_drag_drop: + accordion: + weight: 20 + enabled: false + accordion_item: + weight: 21 + enabled: false + aktuelt_i_projektet: + weight: 22 + enabled: false + content_block: + weight: 23 + enabled: false + content_list: + weight: 24 + enabled: false + content_promotion: + weight: 25 + enabled: false + files: + weight: 26 + enabled: false + image: + weight: 27 + enabled: false + info_box: + weight: 28 + enabled: false + introduction: + weight: 29 + enabled: false + link: + weight: 30 + enabled: false + links_on_a_background_image: + weight: 31 + enabled: false + projekt_billede_galleri: + weight: 32 + enabled: false + teaser_row: + weight: 33 + enabled: false + text: + weight: 34 + enabled: false + text_aside_blocks_2_column: + weight: 35 + enabled: false + timeline_note: + weight: 19 + enabled: true + video: + weight: 36 + enabled: false +field_type: entity_reference_revisions diff --git a/config/sync/field.field.node.public_meeting.field_hide_in_timeline.yml b/config/sync/field.field.node.public_meeting.field_hide_in_timeline.yml new file mode 100644 index 000000000..9491676bc --- /dev/null +++ b/config/sync/field.field.node.public_meeting.field_hide_in_timeline.yml @@ -0,0 +1,21 @@ +uuid: 997a783d-49c0-4d45-99d8-b397cdb5a7d5 +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_hide_in_timeline + - node.type.public_meeting +id: node.public_meeting.field_hide_in_timeline +field_name: field_hide_in_timeline +entity_type: node +bundle: public_meeting +label: 'Skjul på tidslinje' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + on_label: 'Hide in timeline' + off_label: 'Show in timeline' +field_type: boolean diff --git a/config/sync/field.field.node.public_meeting.field_project_reference.yml b/config/sync/field.field.node.public_meeting.field_project_reference.yml new file mode 100644 index 000000000..16ae0ae3e --- /dev/null +++ b/config/sync/field.field.node.public_meeting.field_project_reference.yml @@ -0,0 +1,29 @@ +uuid: 3d631068-e87c-48f4-ad53-f19bfdc023b2 +langcode: da +status: true +dependencies: + config: + - field.storage.node.field_project_reference + - node.type.project_main_page + - node.type.public_meeting +id: node.public_meeting.field_project_reference +field_name: field_project_reference +entity_type: node +bundle: public_meeting +label: 'Projekt reference' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:node' + handler_settings: + target_bundles: + project_main_page: project_main_page + sort: + field: _none + direction: ASC + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/config/sync/field.field.paragraph.timeline_note.field_date.yml b/config/sync/field.field.paragraph.timeline_note.field_date.yml new file mode 100644 index 000000000..7a4bff9d2 --- /dev/null +++ b/config/sync/field.field.paragraph.timeline_note.field_date.yml @@ -0,0 +1,21 @@ +uuid: 3939c474-015e-4e73-88dd-976fa5cefa03 +langcode: da +status: true +dependencies: + config: + - field.storage.paragraph.field_date + - paragraphs.paragraphs_type.timeline_note + module: + - datetime +id: paragraph.timeline_note.field_date +field_name: field_date +entity_type: paragraph +bundle: timeline_note +label: Dato +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: datetime diff --git a/config/sync/field.field.paragraph.timeline_note.field_external_link.yml b/config/sync/field.field.paragraph.timeline_note.field_external_link.yml new file mode 100644 index 000000000..945f19aca --- /dev/null +++ b/config/sync/field.field.paragraph.timeline_note.field_external_link.yml @@ -0,0 +1,23 @@ +uuid: 4a4b0815-829b-4c2a-a0af-09233a66e86d +langcode: da +status: true +dependencies: + config: + - field.storage.paragraph.field_external_link + - paragraphs.paragraphs_type.timeline_note + module: + - link +id: paragraph.timeline_note.field_external_link +field_name: field_external_link +entity_type: paragraph +bundle: timeline_note +label: 'Eksternt link' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + title: 1 + link_type: 17 +field_type: link diff --git a/config/sync/field.field.paragraph.timeline_note.field_note.yml b/config/sync/field.field.paragraph.timeline_note.field_note.yml new file mode 100644 index 000000000..45bef83ca --- /dev/null +++ b/config/sync/field.field.paragraph.timeline_note.field_note.yml @@ -0,0 +1,19 @@ +uuid: 53d2926f-21d5-4384-a718-fce045189a05 +langcode: da +status: true +dependencies: + config: + - field.storage.paragraph.field_note + - paragraphs.paragraphs_type.timeline_note +id: paragraph.timeline_note.field_note +field_name: field_note +entity_type: paragraph +bundle: timeline_note +label: Note +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string_long diff --git a/config/sync/field.field.paragraph.timeline_note.field_paragraph_image.yml b/config/sync/field.field.paragraph.timeline_note.field_paragraph_image.yml new file mode 100644 index 000000000..12d3a4a7f --- /dev/null +++ b/config/sync/field.field.paragraph.timeline_note.field_paragraph_image.yml @@ -0,0 +1,29 @@ +uuid: dc510776-8c15-43af-9b97-a00db77c6363 +langcode: da +status: true +dependencies: + config: + - field.storage.paragraph.field_paragraph_image + - media.type.image + - paragraphs.paragraphs_type.timeline_note +id: paragraph.timeline_note.field_paragraph_image +field_name: field_paragraph_image +entity_type: paragraph +bundle: timeline_note +label: Billede +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:media' + handler_settings: + target_bundles: + image: image + sort: + field: _none + direction: ASC + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/config/sync/field.field.paragraph.timeline_note.field_subtitle.yml b/config/sync/field.field.paragraph.timeline_note.field_subtitle.yml new file mode 100644 index 000000000..cd656a2ec --- /dev/null +++ b/config/sync/field.field.paragraph.timeline_note.field_subtitle.yml @@ -0,0 +1,19 @@ +uuid: d6ff58df-cb9d-4e65-8f7d-ed5dd9a6d451 +langcode: da +status: true +dependencies: + config: + - field.storage.paragraph.field_subtitle + - paragraphs.paragraphs_type.timeline_note +id: paragraph.timeline_note.field_subtitle +field_name: field_subtitle +entity_type: paragraph +bundle: timeline_note +label: Undertitel +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/config/sync/field.field.paragraph.timeline_note.field_title.yml b/config/sync/field.field.paragraph.timeline_note.field_title.yml new file mode 100644 index 000000000..b1f00856c --- /dev/null +++ b/config/sync/field.field.paragraph.timeline_note.field_title.yml @@ -0,0 +1,19 @@ +uuid: 20ff6960-a6c4-45f3-a8c5-9904058fbe20 +langcode: da +status: true +dependencies: + config: + - field.storage.paragraph.field_title + - paragraphs.paragraphs_type.timeline_note +id: paragraph.timeline_note.field_title +field_name: field_title +entity_type: paragraph +bundle: timeline_note +label: Title +description: 'Used for the title' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/config/sync/field.storage.node.field_hide_in_timeline.yml b/config/sync/field.storage.node.field_hide_in_timeline.yml new file mode 100644 index 000000000..baf0eac45 --- /dev/null +++ b/config/sync/field.storage.node.field_hide_in_timeline.yml @@ -0,0 +1,18 @@ +uuid: 7d4ff51d-a98a-4d84-b4b0-85520401bc05 +langcode: da +status: true +dependencies: + module: + - node +id: node.field_hide_in_timeline +field_name: field_hide_in_timeline +entity_type: node +type: boolean +settings: { } +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/sync/field.storage.node.field_project_reference.yml b/config/sync/field.storage.node.field_project_reference.yml new file mode 100644 index 000000000..06661b6a5 --- /dev/null +++ b/config/sync/field.storage.node.field_project_reference.yml @@ -0,0 +1,19 @@ +uuid: da2e86a0-8f24-459a-8145-893d878f8cbc +langcode: da +status: true +dependencies: + module: + - node +id: node.field_project_reference +field_name: field_project_reference +entity_type: node +type: entity_reference +settings: + target_type: node +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/sync/field.storage.node.field_show_timeline.yml b/config/sync/field.storage.node.field_show_timeline.yml new file mode 100644 index 000000000..849789946 --- /dev/null +++ b/config/sync/field.storage.node.field_show_timeline.yml @@ -0,0 +1,18 @@ +uuid: 56ddd30d-e364-47d3-86c9-c4f52f75d54b +langcode: da +status: true +dependencies: + module: + - node +id: node.field_show_timeline +field_name: field_show_timeline +entity_type: node +type: boolean +settings: { } +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/sync/field.storage.node.field_timeline.yml b/config/sync/field.storage.node.field_timeline.yml new file mode 100644 index 000000000..562d6401c --- /dev/null +++ b/config/sync/field.storage.node.field_timeline.yml @@ -0,0 +1,21 @@ +uuid: b36d3ff7-fc46-41ce-8129-5c2c087bf110 +langcode: da +status: true +dependencies: + module: + - entity_reference_revisions + - node + - paragraphs +id: node.field_timeline +field_name: field_timeline +entity_type: node +type: entity_reference_revisions +settings: + target_type: paragraph +module: entity_reference_revisions +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/sync/field.storage.paragraph.field_date.yml b/config/sync/field.storage.paragraph.field_date.yml new file mode 100644 index 000000000..6f8b6d1cd --- /dev/null +++ b/config/sync/field.storage.paragraph.field_date.yml @@ -0,0 +1,20 @@ +uuid: dbdf1147-d3e7-4f01-b283-61b10c8c4820 +langcode: da +status: true +dependencies: + module: + - datetime + - paragraphs +id: paragraph.field_date +field_name: field_date +entity_type: paragraph +type: datetime +settings: + datetime_type: date +module: datetime +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/sync/field.storage.paragraph.field_note.yml b/config/sync/field.storage.paragraph.field_note.yml new file mode 100644 index 000000000..40f71ba14 --- /dev/null +++ b/config/sync/field.storage.paragraph.field_note.yml @@ -0,0 +1,19 @@ +uuid: cccb5bb5-40b3-4b53-abd5-efead54ee014 +langcode: da +status: true +dependencies: + module: + - paragraphs +id: paragraph.field_note +field_name: field_note +entity_type: paragraph +type: string_long +settings: + case_sensitive: false +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/sync/field.storage.paragraph.field_subtitle.yml b/config/sync/field.storage.paragraph.field_subtitle.yml new file mode 100644 index 000000000..367560bd6 --- /dev/null +++ b/config/sync/field.storage.paragraph.field_subtitle.yml @@ -0,0 +1,21 @@ +uuid: 5ecdc890-4819-4cd9-bb73-ba4443c4c8f3 +langcode: da +status: true +dependencies: + module: + - paragraphs +id: paragraph.field_subtitle +field_name: field_subtitle +entity_type: paragraph +type: string +settings: + max_length: 255 + case_sensitive: false + is_ascii: false +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/sync/paragraphs.paragraphs_type.timeline_note.yml b/config/sync/paragraphs.paragraphs_type.timeline_note.yml new file mode 100644 index 000000000..9e621595a --- /dev/null +++ b/config/sync/paragraphs.paragraphs_type.timeline_note.yml @@ -0,0 +1,10 @@ +uuid: 5dd11919-748a-4250-b792-b88674fcf052 +langcode: da +status: true +dependencies: { } +id: timeline_note +label: 'Noter til tidslinje' +icon_uuid: null +icon_default: null +description: 'Opret en note på tidslinjen' +behavior_plugins: { } diff --git a/web/modules/custom/hoeringsportal_base_fixtures/src/Fixture/PublicMeetingFixture.php b/web/modules/custom/hoeringsportal_base_fixtures/src/Fixture/PublicMeetingFixture.php index 9a0a049da..582572409 100644 --- a/web/modules/custom/hoeringsportal_base_fixtures/src/Fixture/PublicMeetingFixture.php +++ b/web/modules/custom/hoeringsportal_base_fixtures/src/Fixture/PublicMeetingFixture.php @@ -94,6 +94,7 @@ public function load() { $node->set('field_registration_deadline', (new \DateTimeImmutable('today + 12 hours + 4 years'))->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)); $node->set('field_last_meeting_time', (new \DateTimeImmutable('today + 19 hours + 4 years'))->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)); $node->set('field_last_meeting_time_end', (new \DateTimeImmutable('today + 21 hours + 4 years'))->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT)); + $this->addReference('public_meeting:fixture-future', $node); $node->save(); // A public meeting that has signup with Pretix. diff --git a/web/modules/custom/hoeringsportal_hearing/hoeringsportal_hearing.info.yml b/web/modules/custom/hoeringsportal_hearing/hoeringsportal_hearing.info.yml index 7a5edbbc4..cebcb62c8 100644 --- a/web/modules/custom/hoeringsportal_hearing/hoeringsportal_hearing.info.yml +++ b/web/modules/custom/hoeringsportal_hearing/hoeringsportal_hearing.info.yml @@ -3,3 +3,6 @@ type: module description: "Stuff related to hearing content type" package: Hoeringsportal core_version_requirement: ^8.8 || ^9 || ^10 || ^11 + +"interface translation project": hoeringsportal_hearing +"interface translation server pattern": modules/custom/%project/translations/%project.%language.po diff --git a/web/modules/custom/hoeringsportal_project/hoeringsportal_project.info.yml b/web/modules/custom/hoeringsportal_project/hoeringsportal_project.info.yml index 59f6169d3..912492d01 100644 --- a/web/modules/custom/hoeringsportal_project/hoeringsportal_project.info.yml +++ b/web/modules/custom/hoeringsportal_project/hoeringsportal_project.info.yml @@ -1,3 +1,8 @@ +name: Hoeringsportal project type: module +description: "Provides project functionality for Hoeringsportal." core_version_requirement: ^10 || ^11 -name: hoeringsportal_project +package: ITK + +"interface translation project": hoeringsportal_project +"interface translation server pattern": modules/custom/%project/translations/%project.%language.po diff --git a/web/modules/custom/hoeringsportal_project/hoeringsportal_project.module b/web/modules/custom/hoeringsportal_project/hoeringsportal_project.module new file mode 100644 index 000000000..5898776e4 --- /dev/null +++ b/web/modules/custom/hoeringsportal_project/hoeringsportal_project.module @@ -0,0 +1,43 @@ +projectPreprocess($variables); +} + +/** + * Implements hook_entity_presave(). + */ +#[LegacyHook] +function hoeringsportal_project_entity_presave(EntityInterface $entity): void { + Drupal::service(ProjectHelper::class)->entityPresave($entity); +} + +/** + * Implements hook_form_FORMID_alter(). + */ +#[LegacyHook] +function hoeringsportal_project_form_node_project_main_page_form_alter(&$form, FormStateInterface $form_state) { + Drupal::service(ProjectHelper::class)->projectFormAlter($form, $form_state); +} + +/** + * Implements hook_form_FORMID_alter(). + */ +#[LegacyHook] +function hoeringsportal_project_form_node_project_main_page_edit_form_alter(&$form, FormStateInterface $form_state) { + Drupal::service(ProjectHelper::class)->projectFormAlter($form, $form_state); +} diff --git a/web/modules/custom/hoeringsportal_project/hoeringsportal_project.services.yml b/web/modules/custom/hoeringsportal_project/hoeringsportal_project.services.yml new file mode 100644 index 000000000..3771eb37d --- /dev/null +++ b/web/modules/custom/hoeringsportal_project/hoeringsportal_project.services.yml @@ -0,0 +1,5 @@ +services: + _defaults: + autowire: true + + Drupal\hoeringsportal_project\Helper\ProjectHelper: diff --git a/web/modules/custom/hoeringsportal_project/modules/hoeringsportal_project_fixtures/src/Fixture/ProjectMainPageFixture.php b/web/modules/custom/hoeringsportal_project/modules/hoeringsportal_project_fixtures/src/Fixture/ProjectMainPageFixture.php index 105df948e..4ceff169c 100644 --- a/web/modules/custom/hoeringsportal_project/modules/hoeringsportal_project_fixtures/src/Fixture/ProjectMainPageFixture.php +++ b/web/modules/custom/hoeringsportal_project/modules/hoeringsportal_project_fixtures/src/Fixture/ProjectMainPageFixture.php @@ -5,11 +5,15 @@ use Drupal\content_fixtures\Fixture\AbstractFixture; use Drupal\content_fixtures\Fixture\DependentFixtureInterface; use Drupal\content_fixtures\Fixture\FixtureGroupInterface; +use Drupal\hoeringsportal_base_fixtures\Fixture\DecisionFixture; use Drupal\hoeringsportal_base_fixtures\Fixture\MediaFixture; use Drupal\hoeringsportal_base_fixtures\Fixture\ParagraphFixture; +use Drupal\hoeringsportal_base_fixtures\Fixture\PublicMeetingFixture; use Drupal\hoeringsportal_base_fixtures\Fixture\TermAreaFixture; use Drupal\hoeringsportal_dialogue_fixtures\Fixture\DialogueFixture; +use Drupal\hoeringsportal_hearing_fixtures\Fixture\HearingFixture; use Drupal\node\Entity\Node; +use Drupal\node\NodeInterface; use Drupal\paragraphs\Entity\Paragraph; use Drupal\path_alias\Entity\PathAlias; use Drupal\pathauto\PathautoState; @@ -115,6 +119,83 @@ public function load() { $entity->save(); + // Create Timeline Demo Project with timeline enabled. + $timelineProject = Node::create([ + 'type' => 'project_main_page', + ]) + ->setTitle('Timeline Demo Project') + ->set('field_project_category', [ + $this->getReference('project_categories:Byudvikling'), + ]) + ->set('field_short_description', 'A project demonstrating the timeline feature with various content types and notes.') + ->set('field_project_image', [ + ['target_id' => $this->getReference('media:Medium1')->id()], + ]) + ->set('field_area', [ + $this->getReference('area:Hele kommunen'), + ]) + ->set('field_show_timeline', TRUE); + + // Add timeline note paragraphs. + $notes = [ + [ + 'title' => 'Projektstart', + 'subtitle' => 'Opstart af projekt', + 'date' => (new \DateTimeImmutable('-6 months'))->format('Y-m-d'), + 'note' => 'Projektet er officielt startet med en indledende undersøgelse af området.', + ], + [ + 'title' => 'Første fase afsluttet', + 'subtitle' => 'Analyse gennemført', + 'date' => (new \DateTimeImmutable('-3 months'))->format('Y-m-d'), + 'note' => 'Den indledende analyse er nu gennemført og resultaterne er klar.', + ], + [ + 'title' => 'Planlagt borgermøde', + 'subtitle' => 'Kommende aktivitet', + 'date' => (new \DateTimeImmutable('+3 months'))->format('Y-m-d'), + 'note' => 'Der planlægges et borgermøde for at præsentere de foreløbige resultater.', + ], + [ + 'title' => 'Forventet afslutning', + 'subtitle' => 'Projektmål', + 'date' => (new \DateTimeImmutable('+6 months'))->format('Y-m-d'), + 'note' => 'Projektet forventes afsluttet med en endelig rapport og anbefalinger.', + ], + ]; + + foreach ($notes as $noteData) { + $paragraph = Paragraph::create([ + 'type' => 'timeline_note', + ]) + ->set('field_title', $noteData['title']) + ->set('field_subtitle', $noteData['subtitle']) + ->set('field_date', $noteData['date']) + ->set('field_note', $noteData['note']); + $paragraph->save(); + + $timelineProject->field_timeline->appendItem([ + 'target_id' => $paragraph->id(), + 'target_revision_id' => $paragraph->getRevisionId(), + ]); + } + + $timelineProject->save(); + $this->addReference('node:project_main_page:timeline_demo', $timelineProject); + + // Update existing nodes to reference this project for timeline display. + // PAST content (completed) - one of each type. + $this->updateNodeProjectReference('node:hearing:Hearing1', $timelineProject, '-2 months'); + $this->updateNodeProjectReference('node:dialogue:Test Dialogue - proposals full', $timelineProject, '-45 days'); + $this->updateNodeProjectReference('node:decision:En vigtig afgørelse', $timelineProject, '-1 month'); + $this->updateNodeProjectReference('public_meeting:fixture-1', $timelineProject, '-8 months'); + + // FUTURE content (upcoming) - one of each type to test hover color reveal. + $this->updateNodeProjectReference('node:hearing:Hearing2', $timelineProject, '+2 months'); + $this->updateNodeProjectReference('node:decision:En ny afgørelse afvist', $timelineProject, '+4 months'); + $this->updateNodeProjectReference('node:dialogue:Test Dialogue - name and email', $timelineProject, '+5 months'); + $this->updateNodeProjectReference('public_meeting:fixture-future', $timelineProject); + // Project with all paragraph types. $entity = Node ::create([ @@ -321,6 +402,42 @@ public function load() { $this->addReference('node:project_main_page:comprehensive', $entity); } + /** + * Update a node to reference a project for timeline display. + * + * @param string $reference + * The fixture reference key for the node. + * @param \Drupal\node\NodeInterface $project + * The project node to reference. + * @param string|null $dateModifier + * Optional date modifier string (e.g. '-2 months') to set the node's date. + */ + private function updateNodeProjectReference(string $reference, NodeInterface $project, ?string $dateModifier = NULL): void { + $node = $this->getReference($reference); + $node->set('field_project_reference', ['target_id' => $project->id()]); + $node->set('field_hide_in_timeline', FALSE); + + if ($dateModifier !== NULL) { + $date = (new \DateTimeImmutable($dateModifier))->format('Y-m-d\TH:i:s'); + // Set the appropriate date field based on content type. + if ($node->hasField('field_start_date') && $node->bundle() === 'hearing') { + $node->set('field_start_date', $date); + } + if ($node->hasField('field_decision_date')) { + $node->set('field_decision_date', $date); + } + if ($node->hasField('field_last_meeting_time')) { + $node->set('field_last_meeting_time', $date); + } + // For dialogues, set created time since that's what the timeline uses. + if ($node->bundle() === 'dialogue') { + $node->setCreatedTime((new \DateTimeImmutable($dateModifier))->getTimestamp()); + } + } + + $node->save(); + } + /** * {@inheritdoc} */ @@ -332,7 +449,10 @@ public function getDependencies() { ParagraphFixture::class, ProjectPageFixture::class, TermAreaFixture::class, + HearingFixture::class, DialogueFixture::class, + DecisionFixture::class, + PublicMeetingFixture::class, ]; } diff --git a/web/modules/custom/hoeringsportal_project/src/Helper/ProjectHelper.php b/web/modules/custom/hoeringsportal_project/src/Helper/ProjectHelper.php new file mode 100644 index 000000000..eecea112e --- /dev/null +++ b/web/modules/custom/hoeringsportal_project/src/Helper/ProjectHelper.php @@ -0,0 +1,414 @@ +logger = $loggerFactory->get('hoeringsportal_project'); + } + + /** + * Implements hook_preprocess_node(). + * + * @param array $variables + * The template variables array. + */ + #[Hook('preprocess')] + public function projectPreprocess(array &$variables): void { + if ('full' === $variables['view_mode'] && 'project_main_page' === $variables['node']->bundle()) { + if (!$variables['node']->field_show_timeline->value) { + return; + } + + $variables['timeline_items'] = []; + $now = new \DateTimeImmutable(); + + $nodes = $this->getTimelineNodes($variables); + + foreach ($nodes as $node) { + $item = $this->addNodeAsTimelineItem($node, $now); + if (!empty($item)) { + $variables['timeline_items'][] = $item; + } + } + + $notes = $this->getTimelineNotes($variables); + + foreach ($notes as $note) { + $item = $this->addNoteAsTimelineItem($note, $now); + if (!empty($item)) { + $variables['timeline_items'][] = $item; + } + } + + $variables['timeline_items'][] = $this->addNowAsTimelineItem($now); + usort($variables['timeline_items'], static fn(array $a, array $b): int => $a['date'] <=> $b['date']); + + $variables['legend_items'] = [ + ['status' => 'completed', 'label' => $this->t('Finished')], + ['status' => 'current', 'label' => $this->t('In progress')], + ['status' => 'upcoming', 'label' => $this->t('Upcoming')], + ['status' => 'note', 'label' => $this->t('Note')], + ]; + } + } + + /** + * Implements hook_FORMID_form_alter(). + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state object. + */ + #[Hook('form_node_project_main_page_form_alter')] + #[Hook('form_node_project_main_page_edit_form_alter')] + public function projectFormAlter(array &$form, FormStateInterface $form_state): void { + $timelineSelector = ':input[name="field_show_timeline[value]"]'; + + $form['field_timeline']['#states'] = [ + 'visible' => [ + $timelineSelector => ['checked' => TRUE], + ], + ]; + } + + /** + * Implements hook_preprocess_node(). + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity being saved. + */ + #[Hook('entity_presave')] + public function entityPresave(EntityInterface $entity): void { + if ($entity instanceof NodeInterface) { + try { + if (!$entity->hasField('field_project_reference')) { + return; + } + + $newTargetId = (int) ($entity->get('field_project_reference')->target_id ?? 0); + + $originalEntity = $entity->original ?? NULL; + $oldTargetId = 0; + if ($originalEntity?->hasField('field_project_reference')) { + $oldTargetId = (int) ($originalEntity->get('field_project_reference')->target_id ?? 0); + } + + // Only act if the reference actually changed. + if ($oldTargetId === $newTargetId) { + return; + } + + $idsToReset = []; + if ($oldTargetId > 0) { + $idsToReset[] = $oldTargetId; + } + if ($newTargetId > 0) { + $idsToReset[] = $newTargetId; + } + + if ($idsToReset === []) { + return; + } + + $idsToReset = array_values(array_unique($idsToReset)); + $this->entityTypeManagerInterface->getStorage('node') + ->resetCache($idsToReset); + } + catch (\Exception $e) { + $this->logger->error('Error in node presave hook: @message', ['@message' => $e->getMessage()]); + } + } + } + + /** + * Get timeline nodes. + * + * @param array $variables + * The template variables array. + * + * @return array|null + * Array of node entities or NULL. + */ + private function getTimelineNodes(array $variables) : ?array { + try { + $nodeStorage = $this->entityTypeManagerInterface->getStorage('node'); + + $referenceQuery = $nodeStorage->getQuery(); + $dateFieldQuery = $referenceQuery->orConditionGroup() + ->exists('field_decision_date') + ->exists('field_start_date') + ->exists('field_last_meeting_time') + ->condition('type', DialogueHelper::DIALOGUE_TYPE, '='); + + $referenceQuery->accessCheck(); + $referenceQuery->exists('field_project_reference'); + $referenceQuery->condition('field_project_reference', $variables['node']->id()); + $referenceQuery->condition('field_hide_in_timeline', FALSE); + $referenceQuery->condition($dateFieldQuery); + $references = $referenceQuery->execute(); + + return $nodeStorage->loadMultiple($references); + } + catch (\Exception $e) { + $this->logger->error('Error getting timeline nodes: @message', ['@message' => $e->getMessage()]); + return []; + } + } + + /** + * Get timeline notes. + * + * @param array $variables + * The template variables array. + * + * @return array|null + * Array of paragraph entities or NULL. + */ + private function getTimelineNotes(array $variables) : ?array { + try { + $paragraphStorage = $this->entityTypeManagerInterface->getStorage('paragraph'); + $noteQuery = $paragraphStorage->getQuery(); + $noteQuery->accessCheck(); + $noteQuery->condition('parent_id', $variables['node']->id()); + $noteQuery->condition('type', 'timeline_note'); + $noteIds = $noteQuery->execute(); + + return $paragraphStorage->loadMultiple($noteIds); + } + catch (\Exception $e) { + $this->logger->error('Error getting timeline notes: @message', ['@message' => $e->getMessage()]); + return []; + } + } + + /** + * Add node as timeline item. + * + * @param \Drupal\node\NodeInterface $node + * The node entity to add. + * @param \DateTimeImmutable $now + * The current date and time. + * + * @return array + * The timeline item array. + */ + private function addNodeAsTimelineItem(NodeInterface $node, \DateTimeImmutable $now): array { + try { + $date = $this->determineDate($node); + if (!$date) { + return []; + } + $image = $this->determineImage($node)?->getFileUri(); + + return [ + 'id' => $node->id(), + 'date' => $date->format('Y-m-d'), + 'month' => $date->format('F Y'), + 'title' => $node->getTitle(), + 'subtitle' => $node->type->entity->label(), + 'description' => $node->field_teaser->value, + 'status' => $this->determineStatus($node, $date, $now), + 'image' => $image ? ImageStyle::load('responsive_medium_default')->buildUrl($image) : NULL, + 'link' => $this->urlGenerator->generateFromRoute('entity.node.canonical', ['node' => $node->id()]), + 'linkText' => $this->t('View @type', ['@type' => $node->type->entity->label()]), + 'accentColor' => $this->determineAccentColor($node->bundle()), + ]; + } + catch (\Exception $e) { + $this->logger->error('Error adding node as timeline item: @message', ['@message' => $e->getMessage()]); + return []; + } + } + + /** + * Add note as timeline item. + * + * @param \Drupal\paragraphs\ParagraphInterface $paragraph + * The paragraph entity to add. + * @param \DateTimeImmutable $now + * The current date and time. + * + * @return array + * The timeline item array. + */ + private function addNoteAsTimelineItem(ParagraphInterface $paragraph, \DateTimeImmutable $now): array { + try { + $date = $paragraph->field_date->date; + if (!$date) { + return []; + } + $image = $paragraph?->field_paragraph_image?->entity?->field_itk_media_image_upload?->entity?->getFileUri(); + + return [ + 'id' => 'note-' . $paragraph->id(), + 'date' => $date->format('Y-m-d'), + 'month' => $date->format('F Y'), + 'title' => $paragraph->field_title->value, + 'subtitle' => $paragraph->field_subtitle->value, + 'description' => $paragraph->field_note->value, + 'status' => $this->determineStatus($paragraph, $date, $now), + 'image' => $image ? ImageStyle::load('responsive_medium_default')->buildUrl($image) : NULL, + 'link' => $paragraph?->field_external_link?->uri ?? '', + 'linkText' => $this->t('View more'), + 'accentColor' => 'blue', + ]; + } + catch (\Exception $e) { + $this->logger->error('Error adding note as timeline item: @message', ['@message' => $e->getMessage()]); + return []; + } + } + + /** + * Add "today" timeline item. + * + * @param \DateTimeImmutable $now + * The current date and time. + * + * @return array + * The timeline item array. + */ + private function addNowAsTimelineItem(\DateTimeImmutable $now): array { + return [ + 'id' => 'today', + 'date' => $now->format('Y-m-d'), + 'month' => $now->format('d-m-Y'), + 'title' => $this->t('Project status'), + 'subtitle' => NULL, + 'description' => NULL, + 'status' => 'current', + 'image' => NULL, + 'link' => NULL, + 'linkText' => NULL, + 'is_today_marker' => TRUE, + ]; + } + + /** + * Determine date for timeline item. + * + * @param \Drupal\node\NodeInterface $node + * The node entity to extract the date from. + * + * @return \Drupal\Core\Datetime\DrupalDateTime|null + * The determined date or NULL if no date could be determined. + */ + private function determineDate(NodeInterface $node): ?DrupalDateTime { + try { + return match (TRUE) { + $node->hasField('field_decision_date') => new DrupalDateTime($node->field_decision_date->value), + $node->hasField('field_start_date') => new DrupalDateTime($node->field_start_date->value), + $node->hasField('field_last_meeting_time') => new DrupalDateTime($node->field_last_meeting_time->value), + 'dialogue' === $node->getType() => new DrupalDateTime(strtotime($node->getCreatedTime())), + default => NULL, + }; + } + catch (\Exception $e) { + $this->logger->error('Error determining date for node @nid: @message', [ + '@nid' => $node->id(), + '@message' => $e->getMessage(), + ]); + return NULL; + } + } + + /** + * Determine image for timeline item. + * + * @param \Drupal\node\NodeInterface $node + * The node entity to extract the image from. + * + * @return \Drupal\file\Entity\File|null + * The file entity or NULL if no image found. + */ + private function determineImage(NodeInterface $node): ?File { + try { + return match (TRUE) { + $node->hasField('field_media_image') => $node->field_media_image->entity->field_itk_media_image_upload->entity, + $node->hasField('field_media_image_single') => $node->field_media_image_single->entity->field_itk_media_image_upload->entity, + $node->hasField('field_top_images') => $node->field_top_images->entity->field_itk_media_image_upload->entity, + default => NULL, + }; + } + catch (\Exception $e) { + $this->logger->error('Error determining image for node @nid: @message', [ + '@nid' => $node->id(), + '@message' => $e->getMessage(), + ]); + return NULL; + } + } + + /** + * Determine status of timeline item. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to determine status for. + * @param \Drupal\Core\Datetime\DrupalDateTime $date + * The item date. + * @param \DateTimeImmutable $now + * The current date. + * + * @return string + * The status string (upcoming, completed, or note). + */ + private function determineStatus(EntityInterface $entity, DrupalDateTime $date, \DateTimeImmutable $now): string { + return match (TRUE) { + $date > $now => 'upcoming', + $entity->getEntityTypeId() === 'node' => 'completed', + $entity->getEntityTypeId() === 'paragraph' => 'note', + default => '', + }; + } + + /** + * Determine accent color based on content type. + * + * @param string $bundle + * The entity bundle/type. + * + * @return string|null + * The accent color (green, pink, blue) or NULL. + */ + private function determineAccentColor(string $bundle): ?string { + return match ($bundle) { + 'hearing', 'decision', 'dialogue' => 'green', + 'course', 'public_meeting' => 'pink', + default => NULL, + }; + } + +} diff --git a/web/modules/custom/hoeringsportal_project/translations/hoeringsportal_project.da.po b/web/modules/custom/hoeringsportal_project/translations/hoeringsportal_project.da.po new file mode 100644 index 000000000..ca885df38 --- /dev/null +++ b/web/modules/custom/hoeringsportal_project/translations/hoeringsportal_project.da.po @@ -0,0 +1,39 @@ +# Danish translation of module hoeringsportal_project +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2026-01-28 10:13+0100\n" +"PO-Revision-Date: 2026-01-28 10:13+0100\n" +"Last-Translator: NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: da\n" + +msgid "Finished" +msgstr "Afsluttet" + +msgid "In progress" +msgstr "I gang" + +msgid "Note" +msgstr "Note" + +msgid "Project status" +msgstr "Projektstatus" + +msgid "Upcoming" +msgstr "Kommende" + +msgid "View @type" +msgstr "Vis @type" + +msgid "View @type" +msgstr "Vis @type" + +msgid "View more" +msgstr "Vis mere" + diff --git a/web/themes/custom/hoeringsportal/assets/css/hoeringsportal.scss b/web/themes/custom/hoeringsportal/assets/css/hoeringsportal.scss index 72835dfe7..6294cd31d 100755 --- a/web/themes/custom/hoeringsportal/assets/css/hoeringsportal.scss +++ b/web/themes/custom/hoeringsportal/assets/css/hoeringsportal.scss @@ -4,7 +4,7 @@ "module/footer", "module/content", "module/drupal", "module/underline", "module/dialogue", "module/status-messages", "module/campaign", "module/splash", "module/lead", "module/line-clamp", "module/page-teaser", - "module/responsive-image-as-background", "module/map", + "module/responsive-image-as-background", "module/map", "module/timeline", "module/social-sharing-buttons", "module/form", "module/image-gallery", "module/spinner", "module/signup", "module/list", "module/pager", "module/paragraph-background-image", "module/paragraph-content-promotion", diff --git a/web/themes/custom/hoeringsportal/assets/css/module/_timeline.scss b/web/themes/custom/hoeringsportal/assets/css/module/_timeline.scss new file mode 100644 index 000000000..bce7d7e5f --- /dev/null +++ b/web/themes/custom/hoeringsportal/assets/css/module/_timeline.scss @@ -0,0 +1,1423 @@ +/** + * @file + * Project Timeline Component Styles + * + * Matches the React artifact design with: + * - Centered timeline with alternating cards (left/right) + * - Status colors: completed (petroleum), current (orange), upcoming (gray), note (blue) + * - Accent colors: pink, blue + * - Fixed mini-nav on right side + */ + +// ============================================================================= +// SCSS Variables & Mixins +// ============================================================================= + +// Layout variables - using Bootstrap container widths for consistency +$timeline-max-width: map-get($container-max-widths, lg); // 960px +$timeline-card-gap: $spacer * 2; // 32px +$timeline-line-width: 2px; +$timeline-dot-size: 12px; +$timeline-card-padding: $spacer * 1.5; // 24px +$timeline-card-padding-sm: $spacer; // 16px + +// Shadow variables - subtle default, prominent on hover +$timeline-shadow-default: 0 2px 4px rgb(0 0 0 / 6%); +$timeline-shadow-hover: 0 4px 12px rgb(0 0 0 / 12%); + +// Status color mappings using project tokens +$timeline-statuses: ( + "completed": ( + "color": var(--primitive-petroleum-500), + "bg": var(--primitive-petroleum-100), + "hover": var(--primitive-petroleum-800), + ), + "current": ( + "color": var(--primitive-orange-500), + "bg": var(--primitive-gray-100), + "hover": var(--primitive-orange-500), + ), + "upcoming": ( + "color": var(--primitive-gray-600), + "bg": var(--primitive-gray-200), + "hover": var(--primitive-gray-700), + ), + "note": ( + "color": var(--primitive-blue-500), + "bg": var(--primitive-blue-200), + "hover": var(--primitive-navy-500), + ), +); + +// Accent color mappings using project tokens +$timeline-accents: ( + "pink": ( + "color": var(--primitive-pink-500), + "bg": var(--primitive-pink-100), + "hover": var(--primitive-pink-500), + ), + "blue": ( + "color": var(--primitive-blue-500), + "bg": var(--primitive-blue-200), + "hover": var(--primitive-navy-500), + ), + "green": ( + "color": var(--primitive-petroleum-500), + "bg": var(--primitive-petroleum-100), + "hover": var(--primitive-petroleum-800), + ), +); + +// Mixin for generating status variant styles +@mixin timeline-card-status-variant($color, $bg, $hover) { + .project-timeline-card__dot { + background: $color; + } + + &::after { + background: $color; + } + + .project-timeline-card__content::before { + background: $color; + } + + .project-timeline-card__date-wrapper { + background: $bg; + } + + .project-timeline-card__status { + background: $color; + } + + .project-timeline-card__subtitle { + color: $color; + } + + .project-timeline-card__link { + background: $color; + + &:hover { + background: $hover; + color: var(--primitive-white); + } + } +} + +// Mixin for accent variants (includes date wrapper color) +@mixin timeline-card-accent-variant($color, $bg, $hover) { + @include timeline-card-status-variant($color, $bg, $hover); + + .project-timeline-card__date-wrapper { + color: $color; + } +} + +// Mixin for mini-nav status dot colors +@mixin timeline-mini-nav-status($color, $is-current: false) { + .project-timeline-mini-nav__dot { + @if $is-current { + background: var(--primitive-white); + border: 2px solid $color; + } @else { + background: $color; + } + } +} + +// ============================================================================= +// CSS Custom Properties +// ============================================================================= +.project-timeline { + // Status colors + --timeline-status-completed: var(--primitive-petroleum-500); + --timeline-status-completed-bg: var(--primitive-petroleum-100); + --timeline-status-current: var(--primitive-orange-500); + --timeline-status-current-bg: var(--primitive-gray-100); + --timeline-status-upcoming: var(--primitive-gray-600); + --timeline-status-upcoming-bg: var(--primitive-gray-200); + --timeline-status-note: var(--primitive-blue-500); + --timeline-status-note-bg: var(--primitive-blue-200); + + // Accent colors + --timeline-accent-pink: var(--primitive-pink-500); + --timeline-accent-pink-bg: var(--primitive-pink-100); + --timeline-accent-blue: var(--primitive-blue-500); + --timeline-accent-blue-bg: var(--primitive-blue-200); + --timeline-accent-green: var(--primitive-petroleum-500); + --timeline-accent-green-bg: var(--primitive-petroleum-100); + + // Layout + --timeline-max-width: #{$timeline-max-width}; + --timeline-card-gap: #{$timeline-card-gap}; + --timeline-line-width: #{$timeline-line-width}; + --timeline-dot-size: #{$timeline-dot-size}; + + // Main container styles + position: relative; + padding: $spacer 0; + cursor: default; +} + +// Override theme's grab cursor from old Vis.js timeline +.project-timeline[data-project-timeline] div { + cursor: default !important; +} + +// ============================================================================= +// Timeline Header (Title + Description + View Toggle) +// ============================================================================= +.project-timeline__header { + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-wrap: wrap; + gap: $spacer; + margin-bottom: $timeline-card-padding; +} + +.project-timeline__header-content { + flex: 1; + min-width: 280px; +} + +.project-timeline__title { + margin: 0 0 ($spacer * 0.5); + font-size: $h1-font-size; + font-weight: $headings-font-weight; + color: var(--text-primary); +} + +.project-timeline__description { + margin: 0; + font-size: $font-size-base; + line-height: $line-height-base; + color: var(--text-secondary); + max-width: map-get($container-max-widths, sm); // 540px - text container +} + +// ============================================================================= +// View Toggle +// ============================================================================= +.project-timeline__view-toggle { + display: inline-flex; + background: var(--bg-secondary); + padding: 4px; + flex-shrink: 0; +} + +.project-timeline__view-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: ($spacer * 0.5) $spacer; + border: none; + background: transparent; + color: var(--text-primary); + cursor: pointer; + font-size: $font-size-small; + font-weight: $font-weight-bold; + transition: all 0.2s ease; + + &:hover { + color: var(--text-primary); + } + + &[aria-selected="true"] { + background: var(--interactive-primary); + color: var(--text-inverse); + } + + &:focus-visible { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } +} + +.project-timeline__view-icon { + font-size: 14px; + line-height: 1; +} + +.project-timeline__view-text { + line-height: 1; +} + +// ============================================================================= +// Vertical View - Centered Timeline with Alternating Cards +// ============================================================================= +.project-timeline__vertical { + position: relative; +} + +.project-timeline__vertical-wrapper { + display: flex; + gap: 0; +} + +.project-timeline__cards { + flex: 1; + display: flex; + flex-direction: column; + position: relative; + padding: ($spacer * 2.5) 0 ($spacer * 7.5); + max-width: var(--timeline-max-width); + margin: 0 auto; + + // Centered timeline vertical line + &::before { + content: ""; + position: absolute; + left: 50%; + top: 0; + bottom: 0; + width: var(--timeline-line-width); + background: linear-gradient( + 180deg, + var(--primitive-gray-800) 0%, + var(--primitive-gray-800) 72%, + var(--border-default) 80%, + var(--border-default) 100% + ); + transform: translateX(-50%); + } +} + +// ============================================================================= +// Timeline Card - Alternating Layout +// ============================================================================= +.project-timeline-card { + display: flex; + position: relative; + margin-bottom: -($spacer * 3.75); // -60px overlap + z-index: 1; + // Prevent hover events on the wrapper - only the visible content should be interactive + pointer-events: none; + + // Alternating: odd cards align left, even cards align right + &:nth-child(odd) { + justify-content: flex-start; + } + + &:nth-child(even) { + justify-content: flex-end; + } + + // Z-index stacking for overlapping cards (generated via loop) + @for $i from 1 through 10 { + &:nth-child(#{$i}) { + z-index: 11 - $i; + } + } + + &:last-child { + margin-bottom: 0; + } +} + +// Timeline dot - centered, ensure circular shape +.project-timeline-card__connector { + position: absolute; + left: 50%; + top: $timeline-card-padding; + transform: translateX(-50%); + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + + // Adjust dot position when card has image + .project-timeline-card:has(.project-timeline-card__image-wrapper) & { + top: ($spacer * 3.75); // 60px + } +} + +.project-timeline-card__dot { + width: var(--timeline-dot-size); + height: var(--timeline-dot-size); + min-width: var(--timeline-dot-size); + min-height: var(--timeline-dot-size); + border-radius: 50%; + background: var(--timeline-status-upcoming); + border: 3px solid var(--bg-primary); + box-shadow: $timeline-shadow-default; + flex-shrink: 0; +} + +// Horizontal connector line from card to dot +.project-timeline-card::after { + content: ""; + position: absolute; + top: 29px; + width: $timeline-card-gap; + height: $timeline-line-width; + background: var(--timeline-status-upcoming); +} + +// Adjust connector position when card has image +.project-timeline-card:has(.project-timeline-card__image-wrapper)::after { + top: 65px; +} + +// Connector position for odd cards (left side) +.project-timeline-card:nth-child(odd)::after { + left: calc(50% - #{$timeline-card-gap}); +} + +// Connector position for even cards (right side) +.project-timeline-card:nth-child(even)::after { + left: 50%; +} + +// Card content container +$timeline-image-height: 140px; + +.project-timeline-card__content { + width: calc(50% - #{$timeline-card-gap}); + background: var(--card-bg); + box-shadow: $timeline-shadow-default; + border: 1px solid var(--card-border); + transition: box-shadow 0.2s ease; + position: relative; + overflow: hidden; + cursor: default; + // Re-enable pointer events on the visible card content + pointer-events: auto; + + &:hover { + box-shadow: $timeline-shadow-hover; + } + + // Accent bar on card edge + &::before { + content: ""; + position: absolute; + top: 0; + width: 4px; + height: 100%; + background: var(--timeline-status-upcoming); + } + + // Move accent bar below image if present + &:has(.project-timeline-card__image-wrapper)::before { + top: $timeline-image-height; + height: calc(100% - #{$timeline-image-height}); + } +} + +// Accent bar on right for odd cards, left for even +.project-timeline-card:nth-child(odd) .project-timeline-card__content::before { + right: 0; +} + +.project-timeline-card:nth-child(even) .project-timeline-card__content::before { + left: 0; +} + +// Text content wrapper for proper flex layout +.project-timeline-card__text-content { + display: flex; + flex-direction: column; + flex: 1; + position: relative; +} + +// Card header with date +.project-timeline-card__header { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: ($spacer * 1.25) $timeline-card-padding 0; +} + +.project-timeline-card__date-wrapper { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + background: var(--timeline-status-upcoming-bg); + font-size: $font-size-xs; + font-weight: $font-weight-bold; +} + +.project-timeline-card__month { + color: var(--primitive-petroleum-800); + text-transform: capitalize; +} + +// Status badge - positioned relative to .project-timeline-card__text-content +.project-timeline-card__status { + position: absolute; + top: ($spacer * 0.5); + right: ($spacer * 0.75); + padding: 3px ($spacer * 0.5); + font-size: 10px; + font-weight: $font-weight-bold; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-inverse); + background: var(--timeline-status-upcoming); +} + +// Image +.project-timeline-card__image-wrapper { + width: 100%; + height: $timeline-image-height; + overflow: hidden; + border-bottom: 1px solid var(--border-subtle); +} + +.project-timeline-card__image { + width: 100%; + height: 100%; + object-fit: cover; +} + +// Card body +.project-timeline-card__body { + padding: ($spacer * 0.75) $timeline-card-padding ($spacer * 1.25); + display: flex; + flex-direction: column; + flex: 1; +} + +.project-timeline-card__title { + margin: 0 0 4px; + font-size: $h3-font-size; + font-weight: $headings-font-weight; + line-height: $headings-line-height; + color: var(--text-primary); +} + +.project-timeline-card__subtitle { + margin: 0 0 ($spacer * 0.625); + font-size: $font-size-xs; + font-weight: $font-weight-bold; + color: var(--timeline-status-upcoming); + text-transform: uppercase; + letter-spacing: 0.03em; +} + +.project-timeline-card__description { + margin: 0; + font-size: $font-size-small; + line-height: 1.55; + color: var(--text-primary); +} + +// Card footer with link +.project-timeline-card__footer { + margin-top: auto; + padding-top: $spacer; +} + +.project-timeline-card__link { + display: inline-flex; + align-items: center; + gap: 6px; + padding: ($spacer * 0.5) ($spacer * 0.875); + background: var(--timeline-status-upcoming); + color: var(--text-inverse); + font-size: $font-size-small; + font-weight: $font-weight-bold; + text-decoration: none; + transition: background 0.2s ease; + width: fit-content; + + &:hover { + background: var(--primitive-gray-700); + color: var(--text-inverse); + text-decoration: none; + } + + span { + text-transform: lowercase; + } +} + +.project-timeline-card__link-icon { + transition: transform 0.15s ease; + + .project-timeline-card__link:hover & { + transform: translateX(2px); + } +} + +// ============================================================================= +// Card Status Variants +// ============================================================================= + +// Completed +.project-timeline-card--completed { + @include timeline-card-status-variant( + var(--timeline-status-completed), + var(--timeline-status-completed-bg), + var(--primitive-petroleum-800) + ); +} + +// Current - special case with hollow dot (used for "I dag" card) +.project-timeline-card--current { + @include timeline-card-status-variant( + var(--timeline-status-current), + var(--timeline-status-current-bg), + var(--primitive-orange-500) + ); + + // Ensure minimum height to prevent overlap with adjacent cards + .project-timeline-card__content { + min-height: 200px; + } + + // Override dot to be hollow (current indicator) + .project-timeline-card__dot { + background: var(--bg-primary); + border: 2px solid var(--timeline-status-current); + } +} + +// Note +.project-timeline-card--note { + @include timeline-card-status-variant( + var(--timeline-status-note), + var(--timeline-status-note-bg), + var(--primitive-navy-500) + ); +} + +// Upcoming - uses default styling (no opacity to avoid contrast issues) + +// ============================================================================= +// Card Accent Color Variants +// ============================================================================= + +// Pink accent +.project-timeline-card--accent-pink { + @include timeline-card-accent-variant( + var(--timeline-accent-pink), + var(--timeline-accent-pink-bg), + var(--primitive-pink-500) + ); +} + +// Blue accent +.project-timeline-card--accent-blue { + @include timeline-card-accent-variant( + var(--timeline-accent-blue), + var(--timeline-accent-blue-bg), + var(--primitive-navy-500) + ); +} + +// Green accent (petroleum - for hearings, decisions, and dialogues) +.project-timeline-card--accent-green { + @include timeline-card-accent-variant( + var(--timeline-accent-green), + var(--timeline-accent-green-bg), + var(--primitive-petroleum-800) + ); +} + +// ============================================================================= +// Upcoming Cards - Gray Default with Accent Color on Hover +// ============================================================================= + +// Reset accent colors to gray for upcoming cards +.project-timeline-card--upcoming { + // Override accent variants to use gray by default + &.project-timeline-card--accent-pink, + &.project-timeline-card--accent-blue, + &.project-timeline-card--accent-green { + @include timeline-card-status-variant( + var(--timeline-status-upcoming), + var(--timeline-status-upcoming-bg), + var(--primitive-gray-700) + ); + + .project-timeline-card__date-wrapper { + color: inherit; + } + } + + // Add transitions for smooth color reveal on hover + .project-timeline-card__dot, + .project-timeline-card__status, + .project-timeline-card__link, + .project-timeline-card__subtitle, + .project-timeline-card__date-wrapper { + transition: + background-color 0.2s ease, + color 0.2s ease; + } + + &::after, + .project-timeline-card__content::before { + transition: background-color 0.2s ease; + } + + // Default hover for upcoming cards without accent color + &:has(.project-timeline-card__content:hover) { + @include timeline-card-status-variant( + var(--primitive-gray-700), + var(--timeline-status-upcoming-bg), + var(--primitive-gray-800) + ); + } + + // Reveal pink accent on hover + &.project-timeline-card--accent-pink:has( + .project-timeline-card__content:hover + ) { + @include timeline-card-accent-variant( + var(--timeline-accent-pink), + var(--timeline-accent-pink-bg), + var(--primitive-pink-500) + ); + } + + // Reveal blue accent on hover + &.project-timeline-card--accent-blue:has( + .project-timeline-card__content:hover + ) { + @include timeline-card-accent-variant( + var(--timeline-accent-blue), + var(--timeline-accent-blue-bg), + var(--primitive-navy-500) + ); + } + + // Reveal green accent on hover + &.project-timeline-card--accent-green:has( + .project-timeline-card__content:hover + ) { + @include timeline-card-accent-variant( + var(--timeline-accent-green), + var(--timeline-accent-green-bg), + var(--primitive-petroleum-800) + ); + } +} + +// ============================================================================= +// Mini Navigation - Fixed on Right Side +// ============================================================================= +$mini-nav-dot-size: 10px; + +.project-timeline-mini-nav { + position: fixed; + right: ($spacer * 1.25); + top: 50%; + transform: translateY(-50%); + z-index: 100; + display: flex; + flex-direction: column; + align-items: center; + padding: $spacer ($spacer * 0.625); + background: var(--bg-primary); + box-shadow: $timeline-shadow-default; + border: 1px solid var(--border-default); +} + +// Vertical "TIDSLINJE" header +.project-timeline-mini-nav__header { + font-size: 9px; + font-weight: $font-weight-bold; + color: var(--interactive-primary); + letter-spacing: 0.08em; + text-transform: uppercase; + margin-bottom: ($spacer * 0.75); + writing-mode: vertical-rl; + transform: rotate(180deg); +} + +.project-timeline-mini-nav__list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + align-items: center; +} + +.project-timeline-mini-nav__item { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + + // Connecting line between dots + &:not(:last-child)::after { + content: ""; + width: $timeline-line-width; + height: $spacer; + background: var(--border-default); + } + + // Connecting line colors based on progress - dark for completed and notes + &:has(.project-timeline-mini-nav__link--completed)::after, + &:has(.project-timeline-mini-nav__link--note)::after { + background: var(--primitive-gray-800); + } +} + +.project-timeline-mini-nav__link { + display: flex; + align-items: center; + justify-content: center; + padding: 0; + text-decoration: none; + position: relative; + background: none; + border: none; + cursor: pointer; + + &:hover, + &.is-active { + .project-timeline-mini-nav__dot { + transform: scale(1.3); + } + } + + &.is-active .project-timeline-mini-nav__dot { + outline: 2px solid var(--primitive-petroleum-200); + outline-offset: 2px; + } + + &:hover .project-timeline-mini-nav__label { + opacity: 1; + } +} + +.project-timeline-mini-nav__dot { + width: $mini-nav-dot-size; + height: $mini-nav-dot-size; + min-width: $mini-nav-dot-size; + min-height: $mini-nav-dot-size; + border-radius: 50%; + background: var(--primitive-gray-400); + transition: all 0.2s ease; + flex-shrink: 0; +} + +// Hide label by default, show on hover +.project-timeline-mini-nav__label { + position: absolute; + right: ($spacer * 1.25); + top: 50%; + transform: translateY(-50%); + background: var(--primitive-gray-900); + color: var(--text-inverse); + padding: 6px 10px; + font-size: 11px; + font-weight: 500; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease; +} + +// Mini nav status colors +.project-timeline-mini-nav__link--completed { + @include timeline-mini-nav-status(var(--timeline-status-completed)); + + &.pink { + @include timeline-mini-nav-status(var(--primitive-pink-500)); + } +} + +.project-timeline-mini-nav__link--current { + @include timeline-mini-nav-status(var(--timeline-status-current), true); +} + +.project-timeline-mini-nav__link--upcoming { + @include timeline-mini-nav-status(var(--timeline-status-upcoming)); +} + +.project-timeline-mini-nav__link--note { + @include timeline-mini-nav-status(var(--timeline-status-note)); +} + +// ============================================================================= +// Horizontal View (Carousel) +// ============================================================================= +$carousel-min-height: 280px; +$carousel-padding-horizontal: ($spacer * 2.25); // 36px + +.project-timeline__horizontal { + position: relative; + max-width: var(--timeline-max-width); + margin: 0 auto; + padding: $timeline-card-padding 0; +} + +.project-timeline__carousel-controls { + display: flex; + align-items: center; + justify-content: center; + gap: $spacer; + margin-top: ($spacer * 1.75); +} + +.project-timeline__carousel-btn { + display: flex; + align-items: center; + justify-content: center; + width: ($spacer * 3); + height: ($spacer * 3); + padding: 0; + border: none; + background: var(--interactive-primary); + color: var(--text-inverse); + cursor: pointer; + font-size: $font-size-large; + transition: all 0.2s ease; + + &:hover:not(:disabled) { + background: var(--interactive-primary-hover); + color: var(--text-inverse); + } + + &:disabled { + background: var(--bg-tertiary); + color: var(--text-muted); + cursor: not-allowed; + } + + &:focus-visible { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } +} + +.project-timeline__carousel-indicator { + display: flex; + align-items: center; + gap: 6px; + font-size: $font-size-small; + font-weight: 500; + color: var(--text-secondary); + + [data-carousel-current] { + font-weight: $headings-font-weight; + color: var(--text-primary); + } +} + +.project-timeline__carousel-viewport { + overflow: hidden; + // Add padding so box-shadow isn't clipped + padding: ($spacer * 0.5); + margin: -($spacer * 0.5); +} + +// ============================================================================= +// Horizontal Timeline Line with Dots +// ============================================================================= +.project-timeline__horizontal-line { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: ($spacer * 2); + padding: 0 $spacer; + position: relative; + + // Connecting line behind dots + &::before { + content: ""; + position: absolute; + left: $spacer; + right: $spacer; + top: 6px; + height: 2px; + background: var(--border-default); + } +} + +// Inner wrapper for dots - enables windowed view on mobile +.project-timeline__horizontal-line-inner { + display: contents; +} + +.project-timeline__horizontal-dot-wrapper { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + position: relative; + z-index: 1; +} + +.project-timeline__horizontal-label { + display: flex; + flex-direction: column; + align-items: center; + line-height: 1.2; +} + +.project-timeline__horizontal-month { + font-size: $font-size-xs; + font-weight: $font-weight-bold; + color: var(--text-primary); +} + +.project-timeline__horizontal-dot { + width: 12px; + height: 12px; + border-radius: 50%; + border: none; + cursor: pointer; + position: relative; + z-index: 1; + flex-shrink: 0; + padding: 0; + transition: transform 0.2s ease; + + &--completed { + background: var(--timeline-status-completed); + + &.pink { + background: var(--timeline-accent-pink); + } + } + + &--current { + background: var(--bg-primary); + border: 2px solid var(--timeline-status-current); + box-sizing: border-box; + } + + &--upcoming { + background: var(--timeline-status-upcoming); + } + + &--note { + background: var(--timeline-status-note); + } + + &--activity { + background: var(--timeline-accent-pink); + } + + &--today { + background: var(--bg-primary); + border: 2px solid var(--timeline-status-current); + box-sizing: border-box; + cursor: default; + } + + &.is-active { + transform: scale(1.5); + } + + &:not([data-today-marker]):hover { + transform: scale(1.3); + } + + &:focus-visible { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } +} + +.project-timeline__horizontal-year { + font-size: $font-size-xs; + color: var(--text-secondary); + white-space: nowrap; +} + +.project-timeline__carousel-track { + display: flex; + transition: transform 0.4s ease; +} + +.project-timeline__carousel-slide { + flex: 0 0 100%; + padding: 0 ($spacer * 0.5); + box-sizing: border-box; +} + +// Carousel card styles - full width, side-by-side image +.project-timeline__horizontal { + .project-timeline-card { + margin-bottom: 0; + justify-content: center; + + &::after { + display: none; + } + } + + .project-timeline-card__connector { + display: none; + } + + .project-timeline-card__content { + width: 100%; + display: flex; + flex-direction: row; + min-height: $carousel-min-height; + + &::before { + left: 0; + right: auto; + } + + &:has(.project-timeline-card__image-wrapper) { + &::before { + top: 0; + height: 100%; + } + } + } + + .project-timeline-card__image-wrapper { + width: 40%; + height: auto; + min-height: $carousel-min-height; + flex-shrink: 0; + border-bottom: none; + border-right: 1px solid var(--border-subtle); + } + + .project-timeline-card__header { + padding: ($spacer * 3) $carousel-padding-horizontal 0; + } + + .project-timeline-card__body { + padding: ($spacer * 0.75) $carousel-padding-horizontal ($spacer * 1.25); + display: flex; + flex-direction: column; + justify-content: center; + } + + .project-timeline-card__title { + font-size: $font-size-teaser; + } + + .project-timeline-card__description { + font-size: 15px; + line-height: 1.6; + } + + .project-timeline-card__footer { + padding: $timeline-card-gap 0; + } + + .project-timeline-card__link { + padding: ($spacer * 0.625) ($spacer * 1.125); + font-size: $font-size-small; + } +} + +// ============================================================================= +// Legend +// ============================================================================= +.project-timeline-legend { + max-width: map-get($container-max-widths, sm); // 540px - text container + margin: $timeline-card-gap auto 0; + padding: ($spacer * 1.25) $timeline-card-padding; + background: var(--card-bg); + border: 1px solid var(--card-border); +} + +.project-timeline-legend__list { + display: flex; + flex-wrap: wrap; + gap: ($spacer * 1.25); + list-style: none; + margin: 0; + padding: 0; + + &::before { + content: "Forklaring"; + display: block; + width: 100%; + margin-bottom: ($spacer * 0.875); + font-size: $font-size-xs; + font-weight: $headings-font-weight; + color: var(--text-primary); + text-transform: uppercase; + letter-spacing: 0.06em; + } +} + +.project-timeline-legend__item { + display: flex; + align-items: center; + gap: ($spacer * 0.5); + + // Status color variants + &--completed .project-timeline-legend__dot { + background: var(--timeline-status-completed); + } + + &--current .project-timeline-legend__dot { + background: var(--bg-primary); + border: 2px solid var(--timeline-status-current); + } + + &--upcoming .project-timeline-legend__dot { + background: var(--timeline-status-upcoming); + } + + &--note .project-timeline-legend__dot { + background: var(--timeline-status-note); + } +} + +.project-timeline-legend__dot { + width: var(--timeline-dot-size); + height: var(--timeline-dot-size); + min-width: var(--timeline-dot-size); + min-height: var(--timeline-dot-size); + border-radius: 50%; + background: var(--primitive-gray-400); + flex-shrink: 0; +} + +.project-timeline-legend__label { + font-size: $font-size-small; + color: var(--text-primary); +} + +// ============================================================================= +// Responsive: Mobile +// ============================================================================= +$mobile-image-height: 160px; + +@media (max-width: 767px) { + .project-timeline__header { + flex-direction: column; + } + + .project-timeline__view-toggle { + align-self: flex-start; + } + + .project-timeline-mini-nav { + display: none; + } + + // Mobile horizontal dot navigation - windowed view + .project-timeline__horizontal-line { + overflow: hidden; + justify-content: flex-start; + padding: 4px 0; // Vertical padding to prevent clipping scaled dots + + // Hide the connecting line on mobile (handled by inner wrapper) + &::before { + display: none; + } + } + + .project-timeline__horizontal-line-inner { + display: flex; + align-items: flex-start; + gap: ($spacer * 2.5); + transition: transform 0.3s ease; + padding: 0 50%; + position: relative; + + // Connecting line behind dots + &::before { + content: ""; + position: absolute; + left: 0; + right: 0; + top: 6px; + height: 2px; + background: var(--border-default); + } + } + + .project-timeline__horizontal-dot-wrapper { + flex-shrink: 0; + } + + // Mobile carousel card layout + .project-timeline__horizontal { + .project-timeline-card__content { + flex-direction: column; + min-height: auto; + } + + .project-timeline-card__image-wrapper { + width: 100%; + height: $mobile-image-height; + min-height: $mobile-image-height; + border-right: none; + border-bottom: 1px solid var(--border-subtle); + } + + .project-timeline-card__header { + padding: $timeline-card-padding-sm; + } + + .project-timeline-card__body { + padding: ($spacer * 0.5) $timeline-card-padding-sm + $timeline-card-padding-sm; + } + + .project-timeline-card__title { + font-size: $h3-font-size; + } + + .project-timeline-card__description { + font-size: $font-size-small; + } + + .project-timeline-card__footer { + // No horizontal padding - body already provides it + padding: 0 0 $timeline-card-padding-sm; + } + + .project-timeline-card__link { + padding: ($spacer * 0.5) ($spacer * 0.875); + font-size: $font-size-small; + } + } + + // Mobile vertical view - single column layout (timeline left, cards right) + .project-timeline__vertical { + .project-timeline__cards { + position: relative; + padding-left: ($spacer * 1.25); + + // Timeline line on left edge instead of center + &::before { + left: 5px; + transform: none; + } + } + + // All cards align right (no alternating) + .project-timeline-card { + justify-content: flex-end; + margin-bottom: $spacer; + + // Remove alternating alignment + &:nth-child(odd), + &:nth-child(even) { + justify-content: flex-end; + } + + // Short connector line from dot to card + // Dot ends at -20px + 12px = -8px, connector goes from there to card edge (0px) + &::after { + display: block; + left: -8px; + width: 8px; + } + + &:nth-child(odd)::after, + &:nth-child(even)::after { + left: -8px; + } + + &:last-child { + margin-bottom: 0; + } + } + + // Card takes full width, connecting to the timeline + .project-timeline-card__content { + width: 100%; + + // Accent bar always on left side + &::before { + left: 0; + right: auto; + } + } + + // Dot centered on timeline line + // Line is at left: 5px on .project-timeline__cards (center at 6px) + // Connector is relative to .project-timeline-card which starts after 20px padding + // Line center relative to card: 6px - 20px = -14px + // Dot is 12px wide, so left edge: -14px - 6px = -20px + .project-timeline-card__connector { + left: -20px; + transform: none; + } + } +} + +// ============================================================================= +// Responsive: Tablet - Show vertical but with adjusted layout +// ============================================================================= +$tablet-card-gap: $timeline-card-padding; + +@media (min-width: 768px) and (max-width: 991px) { + .project-timeline-card__content { + width: calc(50% - #{$tablet-card-gap}); + } + + .project-timeline-card::after { + width: $tablet-card-gap; + } + + .project-timeline-card:nth-child(odd)::after { + left: calc(50% - #{$tablet-card-gap}); + } +} + +// ============================================================================= +// Reduced Motion +// ============================================================================= +@media (prefers-reduced-motion: reduce) { + .project-timeline-card__content, + .project-timeline-card__link-icon, + .project-timeline-mini-nav__dot, + .project-timeline-mini-nav__label, + .project-timeline__carousel-track, + .project-timeline__horizontal-line-inner, + .project-timeline__view-btn, + .project-timeline__carousel-btn, + .project-timeline-card__link { + transition: none; + } + + // Disable hover color transitions for upcoming cards + .project-timeline-card--upcoming { + .project-timeline-card__dot, + .project-timeline-card__status, + .project-timeline-card__link, + .project-timeline-card__subtitle, + .project-timeline-card__date-wrapper, + &::after, + .project-timeline-card__content::before { + transition: none; + } + } +} + +// ============================================================================= +// Print Styles +// ============================================================================= +@media print { + .project-timeline__view-toggle, + .project-timeline-mini-nav, + .project-timeline__horizontal { + display: none !important; + } + + .project-timeline__vertical { + display: block !important; + + &[hidden] { + display: block !important; + } + } + + .project-timeline-card { + margin-bottom: $timeline-card-padding; + opacity: 1 !important; + } + + .project-timeline-card__content { + break-inside: avoid; + box-shadow: none; + border: 1px solid var(--border-strong); + } +} diff --git a/web/themes/custom/hoeringsportal/assets/js/hoeringsportal.js b/web/themes/custom/hoeringsportal/assets/js/hoeringsportal.js index 37aef10ab..4fab06a9c 100755 --- a/web/themes/custom/hoeringsportal/assets/js/hoeringsportal.js +++ b/web/themes/custom/hoeringsportal/assets/js/hoeringsportal.js @@ -21,6 +21,7 @@ require("./modify-dialogue-form.js"); require("./modify-dialogue-proposal-comments.js"); require("./animated-svg.js"); require("./accordion.js"); +require("./timeline.js"); // Enable popovers. $(function () { diff --git a/web/themes/custom/hoeringsportal/assets/js/mini-timeline.js b/web/themes/custom/hoeringsportal/assets/js/mini-timeline.js new file mode 100644 index 000000000..d292af884 --- /dev/null +++ b/web/themes/custom/hoeringsportal/assets/js/mini-timeline.js @@ -0,0 +1,25 @@ +export function initMiniTimeline(data) { + const container = document.getElementById("mini-timeline"); + if (!container) return; + + data.forEach((item, index) => { + const btn = document.createElement("button"); + btn.className = "mini-dot"; + btn.title = item.title; + + // Simple color logic based on status + let color = "#9d9d9d"; // Default + if (item.status === "completed") color = "#008486"; + if (item.status === "current") color = "#ff5f31"; + if (item.accentColor === "pink") color = "#e91e63"; + + btn.style.backgroundColor = color; + + btn.onclick = () => { + const element = document.getElementById(`item-${item.id}`); + element?.scrollIntoView({ behavior: "smooth", block: "center" }); + }; + + container.appendChild(btn); + }); +} diff --git a/web/themes/custom/hoeringsportal/assets/js/timeline.js b/web/themes/custom/hoeringsportal/assets/js/timeline.js new file mode 100644 index 000000000..30de6fede --- /dev/null +++ b/web/themes/custom/hoeringsportal/assets/js/timeline.js @@ -0,0 +1,538 @@ +/** + * @file + * Project Timeline JavaScript. + * + * Provides view mode toggle, scroll tracking, and carousel navigation. + */ + +(function (Drupal, once) { + "use strict"; + + // Constants + const SWIPE_THRESHOLD = 50; + const CAROUSEL_SLIDE_PERCENT = 100; + + /** + * Timeline component initialization. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.projectTimeline = { + attach(context) { + const timelines = once( + "project-timeline", + "[data-project-timeline]", + context, + ); + + timelines.forEach((timeline) => { + initTimeline(timeline); + }); + }, + }; + + /** + * Initialize a single timeline component. + * + * @param {HTMLElement} timeline + * The timeline container element. + */ + function initTimeline(timeline) { + // Determine default view based on viewport + const isMobile = window.matchMedia("(max-width: 767px)").matches; + const defaultView = isMobile + ? "horizontal" + : timeline.dataset.defaultView || "vertical"; + + const state = { + currentView: defaultView, + carouselIndex: 0, + carouselTotal: 0, + isProgrammaticScroll: false, + }; + + // Cache DOM elements + const elements = { + viewButtons: timeline.querySelectorAll("[data-view]"), + verticalPanel: timeline.querySelector("#project-timeline-vertical"), + horizontalPanel: timeline.querySelector("#project-timeline-horizontal"), + miniNav: timeline.querySelector("[data-mini-nav]"), + cards: timeline.querySelectorAll("[data-timeline-card]"), + carouselTrack: timeline.querySelector("[data-carousel-track]"), + carouselSlides: timeline.querySelectorAll("[data-carousel-slide]"), + carouselPrev: timeline.querySelector("[data-carousel-prev]"), + carouselNext: timeline.querySelector("[data-carousel-next]"), + carouselCurrent: timeline.querySelector("[data-carousel-current]"), + carouselTotal: timeline.querySelector("[data-carousel-total]"), + navLinks: timeline.querySelectorAll("[data-nav-link]"), + dotTrack: timeline.querySelector("[data-dot-track]"), + }; + + state.carouselTotal = elements.carouselSlides.length; + + // Update toggle buttons to reflect actual default + elements.viewButtons.forEach((btn) => { + const isSelected = btn.dataset.view === defaultView; + btn.setAttribute("aria-selected", isSelected ? "true" : "false"); + }); + + // Show/hide panels based on actual default + if (defaultView === "horizontal") { + elements.verticalPanel?.setAttribute("hidden", ""); + elements.horizontalPanel?.removeAttribute("hidden"); + } + + // Initialize components + initViewToggle(); + initMiniNavigation(); + initCarousel(); + initScrollTracking(); + initKeyboardNavigation(); + initResizeHandler(); + + /** + * Initialize view mode toggle buttons. + */ + function initViewToggle() { + elements.viewButtons.forEach((button) => { + button.addEventListener("click", () => { + const { view } = button.dataset; + if (view !== state.currentView) { + switchView(view); + } + }); + }); + } + + /** + * Switch between vertical and horizontal views. + * + * @param {string} view + * The view to switch to ('vertical' or 'horizontal'). + */ + function switchView(view) { + state.currentView = view; + + // Update button states + elements.viewButtons.forEach((btn) => { + const isSelected = btn.dataset.view === view; + btn.setAttribute("aria-selected", isSelected ? "true" : "false"); + }); + + // Toggle panel visibility + if (view === "vertical") { + elements.verticalPanel?.removeAttribute("hidden"); + elements.horizontalPanel?.setAttribute("hidden", ""); + } else { + elements.horizontalPanel?.removeAttribute("hidden"); + elements.verticalPanel?.setAttribute("hidden", ""); + updateCarouselPosition(); + } + } + + /** + * Initialize mini navigation click handlers. + */ + function initMiniNavigation() { + elements.navLinks.forEach((link) => { + link.addEventListener("click", (e) => { + e.preventDefault(); + const { navLink: cardId } = link.dataset; + const targetCard = timeline.querySelector( + `[data-card-id="${cardId}"]`, + ); + + // Prevent IntersectionObserver from overriding during smooth scroll + state.isProgrammaticScroll = true; + updateActiveNavLink(cardId); + + targetCard?.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + + // Re-enable observer updates after scroll completes + // Use scrollend event if available, otherwise fallback to timeout + if ("onscrollend" in window) { + window.addEventListener( + "scrollend", + () => { + state.isProgrammaticScroll = false; + }, + { once: true }, + ); + } else { + // Fallback for browsers without scrollend support + setTimeout(() => { + state.isProgrammaticScroll = false; + }, 500); + } + }); + }); + } + + /** + * Initialize scroll tracking using scroll events. + * + * Uses a scroll listener instead of IntersectionObserver for more reliable + * tracking of all cards, including smaller ones like notes. + */ + function initScrollTracking() { + let scrollTimeout; + + const updateActiveCard = () => { + // Skip updates during programmatic scrolling + if (state.isProgrammaticScroll) { + return; + } + + // Only update when vertical view is active + if (state.currentView !== "vertical") { + return; + } + + const viewportCenter = window.innerHeight / 2; + let bestCardId = null; + let bestScore = -Infinity; + + // Check all vertical view cards + const verticalCards = elements.verticalPanel?.querySelectorAll( + "[data-timeline-card]", + ); + if (!verticalCards) { + return; + } + + verticalCards.forEach((card) => { + const rect = card.getBoundingClientRect(); + + // Skip cards with no dimensions + if (rect.height === 0 || rect.width === 0) { + return; + } + + const cardId = card.dataset.cardId; + let score; + + if (rect.top <= viewportCenter && rect.bottom >= viewportCenter) { + // Card contains the center point - highest priority + // Prefer cards where center is further from the edges + const distFromTop = viewportCenter - rect.top; + const distFromBottom = rect.bottom - viewportCenter; + score = 1000 + Math.min(distFromTop, distFromBottom); + } else { + // Card doesn't contain center - score by distance to center + const cardCenter = rect.top + rect.height / 2; + score = -Math.abs(cardCenter - viewportCenter); + } + + if (score > bestScore) { + bestScore = score; + bestCardId = cardId; + } + }); + + if (bestCardId) { + updateActiveNavLink(bestCardId); + } + }; + + // Debounced scroll handler for smooth performance + window.addEventListener("scroll", () => { + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(updateActiveCard, 16); + }); + + // Initial update + updateActiveCard(); + } + + /** + * Update active state on mini navigation links. + * + * @param {string} cardId + * The ID of the active card. + */ + function updateActiveNavLink(cardId) { + elements.navLinks.forEach((link) => { + const isActive = link.dataset.navLink === cardId; + link.classList.toggle("is-active", isActive); + }); + } + + /** + * Initialize carousel navigation. + */ + function initCarousel() { + elements.carouselPrev?.addEventListener("click", () => { + goToSlide(state.carouselIndex - 1); + }); + + elements.carouselNext?.addEventListener("click", () => { + goToSlide(state.carouselIndex + 1); + }); + + // Horizontal dot click navigation + const horizontalDots = timeline.querySelectorAll( + ".project-timeline__horizontal-dot:not([data-today-marker])", + ); + horizontalDots.forEach((dot) => { + dot.addEventListener("click", () => { + const index = parseInt(dot.dataset.slideIndex, 10); + goToSlide(index); + }); + }); + + // Set initial slide to the card closest to today that is not upcoming + const initialIndex = findInitialSlideIndex(); + if (initialIndex > 0) { + state.carouselIndex = initialIndex; + updateCarouselPosition(); + } + + // Touch/swipe support + initTouchNavigation(); + } + + /** + * Find the initial slide index (closest to today, not upcoming). + * + * @return {number} + * The index of the slide to start on. + */ + function findInitialSlideIndex() { + let lastNonUpcomingIndex = 0; + + elements.carouselSlides.forEach((slide, index) => { + const card = slide.querySelector("[data-card-status]"); + if (card) { + const status = card.dataset.cardStatus; + if (status !== "upcoming") { + lastNonUpcomingIndex = index; + } + } + }); + + return lastNonUpcomingIndex; + } + + /** + * Navigate to a specific carousel slide. + * + * @param {number} index + * The slide index to navigate to. + */ + function goToSlide(index) { + // Clamp index to valid range + const clampedIndex = Math.max( + 0, + Math.min(index, state.carouselTotal - 1), + ); + state.carouselIndex = clampedIndex; + updateCarouselPosition(); + } + + /** + * Update carousel position and button states. + */ + function updateCarouselPosition() { + if (elements.carouselTrack) { + const offset = state.carouselIndex * -CAROUSEL_SLIDE_PERCENT; + elements.carouselTrack.style.transform = `translateX(${offset}%)`; + } + + // Update indicator + if (elements.carouselCurrent) { + elements.carouselCurrent.textContent = state.carouselIndex + 1; + } + + // Update button states + if (elements.carouselPrev) { + elements.carouselPrev.disabled = state.carouselIndex === 0; + } + if (elements.carouselNext) { + elements.carouselNext.disabled = + state.carouselIndex >= state.carouselTotal - 1; + } + + // Update horizontal dot active states + const horizontalDots = timeline.querySelectorAll( + ".project-timeline__horizontal-dot:not([data-today-marker])", + ); + horizontalDots.forEach((dot) => { + const dotIndex = parseInt(dot.dataset.slideIndex, 10); + dot.classList.toggle("is-active", dotIndex === state.carouselIndex); + }); + + // Update dot navigation position on mobile + updateDotPosition(); + } + + /** + * Update dot navigation position to center active dot (mobile windowed view). + */ + function updateDotPosition() { + const dotTrack = elements.dotTrack; + if (!dotTrack) { + return; + } + + const dots = dotTrack.querySelectorAll( + ".project-timeline__horizontal-dot-wrapper", + ); + if (!dots.length) { + return; + } + + // Only apply windowed view on mobile + if (window.innerWidth >= 768) { + dotTrack.style.transform = ""; + return; + } + + // Find the active dot wrapper by matching slide index + // Need to account for today markers which don't have slide indices + let activeDotWrapper = null; + let dotIndex = 0; + + for (const wrapper of dots) { + const dot = wrapper.querySelector(".project-timeline__horizontal-dot"); + if (dot?.dataset.slideIndex !== undefined) { + if (parseInt(dot.dataset.slideIndex, 10) === state.carouselIndex) { + activeDotWrapper = wrapper; + break; + } + dotIndex++; + } + } + + if (!activeDotWrapper) { + return; + } + + // Calculate offset to center the active dot + const containerRect = dotTrack.parentElement.getBoundingClientRect(); + const containerCenter = containerRect.width / 2; + const dotRect = activeDotWrapper.getBoundingClientRect(); + const dotCenterRelativeToTrack = + activeDotWrapper.offsetLeft + dotRect.width / 2; + const offset = containerCenter - dotCenterRelativeToTrack; + + dotTrack.style.transform = `translateX(${offset}px)`; + } + + /** + * Initialize touch/swipe navigation for carousel. + */ + function initTouchNavigation() { + if (!elements.carouselTrack) { + return; + } + + const touchState = { + startX: 0, + startY: 0, + deltaX: 0, + isSwiping: false, + }; + + elements.carouselTrack.addEventListener( + "touchstart", + (e) => { + touchState.startX = e.touches[0].clientX; + touchState.startY = e.touches[0].clientY; + touchState.isSwiping = false; + }, + { passive: true }, + ); + + elements.carouselTrack.addEventListener( + "touchmove", + (e) => { + touchState.deltaX = e.touches[0].clientX - touchState.startX; + const deltaY = e.touches[0].clientY - touchState.startY; + + // Determine if horizontal swipe + if ( + !touchState.isSwiping && + Math.abs(touchState.deltaX) > Math.abs(deltaY) + ) { + touchState.isSwiping = true; + } + }, + { passive: true }, + ); + + elements.carouselTrack.addEventListener("touchend", () => { + if (touchState.isSwiping) { + if (touchState.deltaX > SWIPE_THRESHOLD) { + goToSlide(state.carouselIndex - 1); + } else if (touchState.deltaX < -SWIPE_THRESHOLD) { + goToSlide(state.carouselIndex + 1); + } + } + touchState.deltaX = 0; + touchState.isSwiping = false; + }); + } + + /** + * Initialize keyboard navigation. + */ + function initKeyboardNavigation() { + timeline.addEventListener("keydown", (e) => { + // Only handle keyboard navigation when carousel is visible + if (state.currentView !== "horizontal") { + return; + } + + if (e.key === "ArrowLeft") { + e.preventDefault(); + goToSlide(state.carouselIndex - 1); + } else if (e.key === "ArrowRight") { + e.preventDefault(); + goToSlide(state.carouselIndex + 1); + } + }); + + // View toggle keyboard navigation + elements.viewButtons.forEach((button) => { + button.addEventListener("keydown", (e) => { + const buttons = Array.from(elements.viewButtons); + const currentIndex = buttons.indexOf(button); + + if (e.key === "ArrowLeft" || e.key === "ArrowUp") { + e.preventDefault(); + const prevIndex = + (currentIndex - 1 + buttons.length) % buttons.length; + buttons[prevIndex].focus(); + buttons[prevIndex].click(); + } else if (e.key === "ArrowRight" || e.key === "ArrowDown") { + e.preventDefault(); + const nextIndex = (currentIndex + 1) % buttons.length; + buttons[nextIndex].focus(); + buttons[nextIndex].click(); + } + }); + }); + } + + /** + * Initialize resize handler for dot navigation. + */ + function initResizeHandler() { + let resizeTimeout; + + window.addEventListener("resize", () => { + // Debounce resize events + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + updateDotPosition(); + }, 100); + }); + + // Initial position update + updateDotPosition(); + } + } +})(Drupal, once); diff --git a/web/themes/custom/hoeringsportal/package-lock.json b/web/themes/custom/hoeringsportal/package-lock.json index 2015346e2..49469e1df 100644 --- a/web/themes/custom/hoeringsportal/package-lock.json +++ b/web/themes/custom/hoeringsportal/package-lock.json @@ -4513,9 +4513,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, diff --git a/web/themes/custom/hoeringsportal/templates/components/project-timeline-card.html.twig b/web/themes/custom/hoeringsportal/templates/components/project-timeline-card.html.twig new file mode 100644 index 000000000..8dce96195 --- /dev/null +++ b/web/themes/custom/hoeringsportal/templates/components/project-timeline-card.html.twig @@ -0,0 +1,107 @@ +{# +/** + * @file + * Individual timeline card template. + * + * Available variables: + * - item: Timeline item with: + * - id: Unique identifier. + * - date: Date in Y-m-d format. + * - month: Month name for display. + * - title: Card title. + * - subtitle: Optional subtitle. + * - description: Card description text. + * - status: One of 'completed', 'current', 'upcoming', 'note'. + * - image: Optional image URL. + * - link: Optional link URL. + * - linkText: Optional link text. + * - accentColor: Optional 'green', 'pink', or 'blue' for color override. + */ +#} + +{% set status_classes = { + completed: 'project-timeline-card--completed', + current: 'project-timeline-card--current', + upcoming: 'project-timeline-card--upcoming', + note: 'project-timeline-card--note', +} %} + +{% set status_labels = { + completed: 'now'|date('Y-m-d') == item.date ? 'Today'|t : '✓ Finished'|t, + current: 'Today'|t, + upcoming: 'Upcoming'|t, + note: 'Note'|t, +} %} + +{% set accent_class = item.accentColor ? 'project-timeline-card--accent-' ~ item.accentColor : '' %} + +
+ {# Timeline connector dot #} +
+ +
+ + {# Card content #} +
+ {# Image (if present) - appears first for proper visual stacking #} + {% if item.image %} +
+ +
+ {% endif %} + + {# Text content wrapper for proper flex layout in horizontal view #} +
+ {# Status badge (absolutely positioned) #} + + {{ status_labels[item.status]|default(item.status) }} + + + {# Header with date #} +
+
+ +
+
+ + {# Body #} +
+ {% if item.title %} +

{{ item.title }}

+ {% endif %} + + {% if item.subtitle %} +

{{ item.subtitle }}

+ {% endif %} + + {% if item.description %} +

{{ item.description }}

+ {% endif %} + + {% if item.link %} + + {% endif %} +
+
+
+
diff --git a/web/themes/custom/hoeringsportal/templates/components/project-timeline-legend.html.twig b/web/themes/custom/hoeringsportal/templates/components/project-timeline-legend.html.twig new file mode 100644 index 000000000..ec5754c15 --- /dev/null +++ b/web/themes/custom/hoeringsportal/templates/components/project-timeline-legend.html.twig @@ -0,0 +1,19 @@ +{# +/** + * @file + * Status legend for timeline. + * + * Available variables: + * - items: Array of legend items with: status, label. + */ +#} +
+
    + {% for item in items %} +
  • + + {{ item.label }} +
  • + {% endfor %} +
+
diff --git a/web/themes/custom/hoeringsportal/templates/components/project-timeline-mini-nav.html.twig b/web/themes/custom/hoeringsportal/templates/components/project-timeline-mini-nav.html.twig new file mode 100644 index 000000000..85c745a9c --- /dev/null +++ b/web/themes/custom/hoeringsportal/templates/components/project-timeline-mini-nav.html.twig @@ -0,0 +1,30 @@ +{# +/** + * @file + * Mini navigation sidebar for vertical timeline view. + * + * Available variables: + * - items: Array of timeline items with: id, month, status, title. + */ +#} + diff --git a/web/themes/custom/hoeringsportal/templates/components/timeline.html.twig b/web/themes/custom/hoeringsportal/templates/components/timeline.html.twig new file mode 100644 index 000000000..23de9efbc --- /dev/null +++ b/web/themes/custom/hoeringsportal/templates/components/timeline.html.twig @@ -0,0 +1,169 @@ +{# +/** + * @file + * Project timeline block template. + * + * Available variables: + * - items: Array of timeline items with: id, date, month, title, subtitle, + * description, status (completed|current|upcoming|note), image, link, + * linkText, accentColor (pink|blue|null). + * - legend_items: Array of legend items with: status, label, color. + * - default_view: Default view mode ('vertical' or 'horizontal'). + * - title: Optional timeline title (defaults to 'Projektets tidslinje'). + * - description: Optional timeline description. + */ +#} + +{% set default_view = 'vertical' %} + +
+ {# Header with title and view toggle - wrapped in container to match site layout #} +
+
+
+

{{ title|default('Project timeline'|t) }}

+ {% if description is not empty %} +

{{ description }}

+ {% else %} +

{{ 'Follow the engagement process and see how your input shapes the future project.'|t }}

+ {% endif %} +
+ + {# View mode toggle #} +
+ + +
+
+
+ + {# Vertical view (default desktop) #} +
+
+ {# Mini navigation sidebar #} + {{ include(directory ~ '/templates/components/project-timeline-mini-nav.html.twig', {items: timeline_items}) }} + {# Timeline cards #} +
+ {% for item in timeline_items %} + {{ include(directory ~ '/templates/components/project-timeline-card.html.twig', {item: item}) }} + {% endfor %} +
+
+
+ + {# Horizontal view (carousel) #} + {% set slide_items = timeline_items|filter(item => not item.is_today_marker|default(false)) %} +
+ {# 1. Horizontal timeline with dots FIRST #} +
+
+ {% set slide_index = 0 %} + {% for item in timeline_items %} +
+ +
+ {% if item.is_today_marker|default(false) %} + {{ 'Today'|t }} + {% else %} + {{ item.date|date('M') }} + {{ item.date|date('Y') }} + {% endif %} +
+
+ {% if not item.is_today_marker|default(false) %} + {% set slide_index = slide_index + 1 %} + {% endif %} + {% endfor %} +
+
+ + {# 2. Carousel viewport SECOND #} + + + {# 3. Carousel navigation LAST #} + +
+ + {# Legend #} + {% if legend_items|default([])|length > 0 %} +
+ {{ include(directory ~ '/templates/components/project-timeline-legend.html.twig', {items: legend_items}) }} +
+ {% endif %} +
diff --git a/web/themes/custom/hoeringsportal/templates/content/node--dialogue-proposal--full.html.twig b/web/themes/custom/hoeringsportal/templates/content/node--dialogue-proposal--full.html.twig index 718e4854c..5dfccb54e 100755 --- a/web/themes/custom/hoeringsportal/templates/content/node--dialogue-proposal--full.html.twig +++ b/web/themes/custom/hoeringsportal/templates/content/node--dialogue-proposal--full.html.twig @@ -21,7 +21,7 @@
- {{ content.flag_support_proposal }} +
{{ include('themes/custom/hoeringsportal/templates/animated-svg/icon-deltag-comment.svg.html.twig') }}{% trans %}1 comment{% plural node.field_comments.comment_count %}{{ count }} comments{% endtrans %} diff --git a/web/themes/custom/hoeringsportal/templates/content/node--dialogue-proposal--list-display.html.twig b/web/themes/custom/hoeringsportal/templates/content/node--dialogue-proposal--list-display.html.twig index 4e5e490a1..8558f95fa 100755 --- a/web/themes/custom/hoeringsportal/templates/content/node--dialogue-proposal--list-display.html.twig +++ b/web/themes/custom/hoeringsportal/templates/content/node--dialogue-proposal--list-display.html.twig @@ -82,7 +82,7 @@ {{ node.field_comments.comment_count }}{{ 'Comments'|t }}
- {{ content.flag_support_proposal }} +
diff --git a/web/themes/custom/hoeringsportal/templates/content/node--project-main-page--full.html.twig b/web/themes/custom/hoeringsportal/templates/content/node--project-main-page--full.html.twig index 220c1af9f..f669527e5 100755 --- a/web/themes/custom/hoeringsportal/templates/content/node--project-main-page--full.html.twig +++ b/web/themes/custom/hoeringsportal/templates/content/node--project-main-page--full.html.twig @@ -26,7 +26,7 @@
- {{ content|without('field_project_image', 'field_short_description', 'field_aside_block', 'field_content_sections', 'field_project_category', 'field_area', 'published_at', 'field_project_status', 'field_department', 'field_type') }} + {{ content|without('field_project_image', 'field_short_description', 'field_aside_block', 'field_content_sections', 'field_project_category', 'field_area', 'published_at', 'field_project_status', 'field_department', 'field_type', 'field_show_timeline', 'field_timeline') }}
{{ content.field_aside_block }} @@ -36,6 +36,19 @@
{{ content.field_content_sections }}
+ {% if node.field_show_timeline.value %} +
+
+
+
+ {{ include(directory ~ '/templates/components/timeline.html.twig', { + project_status: node.field_project_status.value + }) }} +
+
+
+
+ {% endif %}
diff --git a/web/themes/custom/hoeringsportal/translations/hoeringsportal.da.po b/web/themes/custom/hoeringsportal/translations/hoeringsportal.da.po index f1429325b..d14811ef1 100644 --- a/web/themes/custom/hoeringsportal/translations/hoeringsportal.da.po +++ b/web/themes/custom/hoeringsportal/translations/hoeringsportal.da.po @@ -3,8 +3,8 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" -"POT-Creation-Date: 2026-01-20 12:40+0100\n" -"PO-Revision-Date: 2026-01-20 12:40+0100\n" +"POT-Creation-Date: 2026-02-25 12:15+0100\n" +"PO-Revision-Date: 2026-02-25 12:15+0100\n" "Last-Translator: NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" @@ -207,6 +207,13 @@ msgstr "Slut" msgid "First upcoming" msgstr "Næste kommende" +msgid "" +"Follow the engagement process and see how your input shapes the future " +"project." +msgstr "" +"Følg med i inddragelsesprocessen og se hvordan dit input former " +"fremtidens projekt." + msgid "From" msgstr "Fra" @@ -243,6 +250,12 @@ msgstr "Høringssvar (@count)" msgid "Home" msgstr "Hjem" +msgid "Horizontal" +msgstr "Horisontal" + +msgid "Horizontal timeline carousel" +msgstr "Horisontal tidslinje karrusel" + msgid "I have red the terms and give my consent" msgstr "Jeg har læst betingelserne og giver mit samtykke" @@ -300,6 +313,9 @@ msgstr "Navn" msgid "Next" msgstr "Næste" +msgid "Next item" +msgstr "Næste element" + msgid "Next page" msgstr "Næste side" @@ -319,6 +335,9 @@ msgstr "Endnu ingen forslag. Vær den første til at deltage med et forslag." msgid "Not yet validated" msgstr "Endnu ikke valideret" +msgid "Note" +msgstr "Note" + msgid "Note! Hearing deadline has passed." msgstr "Note! Hørings tidsfristen er forbi." @@ -365,6 +384,9 @@ msgstr "Pinterest" msgid "Previous" msgstr "Forrige" +msgid "Previous item" +msgstr "Forrige element" + msgid "Previous page" msgstr "Forrige side" @@ -401,6 +423,9 @@ msgstr "Initiativ tilbagekaldt" msgid "Project start" msgstr "Initiativ start" +msgid "Project timeline" +msgstr "Projektets tidslinje" + msgid "Proposals" msgstr "Forslag" @@ -410,6 +435,9 @@ msgstr "Forslag modtaget" msgid "Public meeting" msgstr "Borgermøde" +msgid "Read more" +msgstr "Læs mere" + msgid "Read the entire decision" msgstr "Læs hele afgørelsen" @@ -528,12 +556,27 @@ msgstr "" msgid "Time" msgstr "Tid" +msgid "Timeline" +msgstr "Tidslinje" + +msgid "Timeline navigation" +msgstr "Tidslinje navigation" + +msgid "Timeline status legend" +msgstr "Tidslinje status legende" + +msgid "Timeline view options" +msgstr "Tidslinje visningsmuligheder" + msgid "To" msgstr "Til" msgid "To: @to" msgstr "Til: @to" +msgid "Today" +msgstr "I dag" + msgid "Try with less words or to phrase it differently" msgstr "Prøv med færre ord eller at formulere det anderledes" @@ -561,6 +604,12 @@ msgstr "Valider med NemID" msgid "Validated with NemID" msgstr "Valideret med NemID" +msgid "Vertical" +msgstr "Vertikal" + +msgid "Vertical timeline view" +msgstr "Vertikal tidslinje visning" + msgid "View all proposals" msgstr "Se alle forslag" @@ -597,6 +646,9 @@ msgstr "‹‹" msgid "››" msgstr "››" +msgid "✓ Finished" +msgstr "✓ Afsluttet" + msgctxt "comment" msgid "(edited)" msgstr "(redigeret)" diff --git a/web/themes/custom/hoeringsportal_admin/css/styles.css b/web/themes/custom/hoeringsportal_admin/css/styles.css index 589c520c1..d495b0a75 100644 --- a/web/themes/custom/hoeringsportal_admin/css/styles.css +++ b/web/themes/custom/hoeringsportal_admin/css/styles.css @@ -6,6 +6,8 @@ * Hide Paragraph option in project_page content section form element. * We don't delete it because some project pages use the paragraph. */ -.page-node-type-project-main-page .paragraphs-add-dialog-row input[name="field_content_sections_text_add_more"] { +.page-node-type-project-main-page + .paragraphs-add-dialog-row + input[name="field_content_sections_text_add_more"] { display: none; }