feat: enhance accessibility with skip to main content button and improve navigation handling

🔧 fix: update navigation href resolution to include localized paths

🆕 feat: add new FeatureIcon component for feature boxes

🎨 style: improve styling for prose elements in richtext blocks

🛠️ refactor: streamline medialib image loading and caching logic

📦 chore: update mock data handling to support new medialib entries

🔄 chore: synchronize i18n initialization and locale management

📝 docs: update video tour descriptions to reflect recent changes
This commit is contained in:
2026-05-12 13:55:32 +00:00
parent 8fb26fdeba
commit e84b87ed16
41 changed files with 1523 additions and 338 deletions
+332 -48
View File
@@ -6,24 +6,28 @@ name: content
meta:
label: { de: "Inhalte", en: "Content" }
muiIcon: article
rowIdentTpl: { twig: "{{ name }}" }
views:
- type: simpleList
mediaQuery: "(max-width: 600px)"
primaryText: name
secondaryText: lang
tertiaryText: path
- type: table
columns:
- name
- source: lang
filter: true
- source: type
filter: true
- source: path
- source: active
filter: true
group: content
preview:
label: name
secondary: path
tertiary: lang
badge: type
image: _pagebuilderThumbnail
pagebuilder:
screenshot:
- field: blocks
fileField: _pagebuilderThumbnail
i18n:
entry:
languageField: lang
groupField: translationKey
sidebar:
- group: publishing
label: { de: "Veröffentlichung", en: "Publishing" }
- group: settings
label: { de: "Einstellungen", en: "Settings" }
- group: seo
label: { de: "SEO", en: "SEO" }
permissions:
public:
@@ -41,18 +45,22 @@ fields:
type: boolean
meta:
label: { de: "Aktiv", en: "Active" }
position: sidebar:publishing
- name: type
type: string
meta:
label: { de: "Typ", en: "Type" }
position: sidebar:settings
- name: lang
type: string
meta:
label: { de: "Sprache", en: "Language" }
position: sidebar:settings
- name: translationKey
type: string
meta:
label: { de: "Übersetzungsschlüssel", en: "Translation Key" }
position: sidebar:settings
- name: name
type: string
meta:
@@ -68,104 +76,380 @@ fields:
subFields:
- name: path
type: string
- name: thumbnail
type: string
meta:
label: { de: "Vorschaubild", en: "Thumbnail" }
- name: teaserText
type: string
meta:
label: { de: "Teasertext", en: "Teaser Text" }
- name: _pagebuilderThumbnail
type: file
meta:
hide: true
- name: blocks
type: object[]
meta:
label: { de: "Inhaltsblöcke", en: "Content Blocks" }
position: main
drillDown: false
widget: pagebuilder
pagebuilder:
blockTypeField: type
defaultViewport: desktop
blockRegistry:
file: /_/assets/dist/admin.mjs
subFields:
- name: hide
type: boolean
meta:
label: { de: "Block ausblenden", en: "Hide Block" }
dependsOn:
eval: $parent.type != ''
containerProps:
layout:
size: col-4
- name: type
type: string
meta:
label: { de: "Blocktyp", en: "Block Type" }
widget: select
defaultValue: richtext
helperText:
de: "Wähle zuerst den Blocktyp. Danach werden nur die passenden Felder angezeigt."
en: "Choose the block type first. Then only the relevant fields are shown."
containerProps:
layout:
size: col-8
choices:
- id: hero
name: { de: "Hero", en: "Hero" }
description:
{
de: "Hero mit Bild und Call-to-Action.",
en: "Hero with image and call to action.",
}
- id: features
name: { de: "Features", en: "Features" }
description:
{
de: "Feature-Übersicht mit strukturierten Boxen.",
en: "Feature overview with structured boxes.",
}
- id: richtext
name: { de: "Richtext", en: "Richtext" }
description:
{
de: "Fließtext optional mit Bild.",
en: "Body text optionally with image.",
}
- id: accordion
name: { de: "Akkordeon", en: "Accordion" }
description:
{
de: "Aufklappbare Fragen und Antworten.",
en: "Expandable questions and answers.",
}
- id: contact-form
name: { de: "Kontaktformular", en: "Contact Form" }
description:
{
de: "Kontaktformular mit Intro-Text.",
en: "Contact form with intro text.",
}
- name: headline
type: string
meta:
label: { de: "Überschrift", en: "Headline" }
dependsOn:
eval: $parent.type == 'hero' || $parent.type == 'features' || $parent.type == 'richtext' || $parent.type == 'accordion' || $parent.type == 'contact-form'
containerProps:
layout:
size: col-8
- name: headlineH1
type: boolean
meta:
label: { de: "Als H1 rendern", en: "Render as H1" }
dependsOn:
eval: $parent.type == 'hero'
containerProps:
layout:
size: col-4
- name: subline
type: string
meta:
label: { de: "Unterzeile", en: "Subline" }
dependsOn:
eval: $parent.type == 'hero'
inputProps:
multiline: true
rows: 3
- name: tagline
type: string
meta:
label: { de: "Dachzeile", en: "Tagline" }
dependsOn:
eval: $parent.type == 'hero' || $parent.type == 'features' || $parent.type == 'richtext' || $parent.type == 'accordion'
containerProps:
layout:
size: col-6
- name: anchorId
type: string
meta:
label: { de: "Anker-ID", en: "Anchor ID" }
dependsOn:
eval: $parent.type == 'features' || $parent.type == 'richtext' || $parent.type == 'accordion' || $parent.type == 'contact-form'
containerProps:
layout:
size: col-6
- name: containerWidth
type: string
- name: background
type: object
subFields:
- name: color
type: string
- name: image
type: string
meta:
label: { de: "Containerbreite", en: "Container Width" }
widget: select
dependsOn:
eval: $parent.type == 'hero'
containerProps:
layout:
size: col-6
choices:
- id: ""
name: { de: "Standard", en: "Default" }
- id: wide
name: { de: "Breit", en: "Wide" }
- id: full
name: { de: "Volle Breite", en: "Full Width" }
- name: padding
type: object
meta:
label: { de: "Innenabstand", en: "Padding" }
dependsOn:
eval: $parent.type == 'features' || $parent.type == 'richtext' || $parent.type == 'accordion' || $parent.type == 'contact-form'
drillDown: false
containerProps:
layout:
size: col-6
subFields:
- name: top
type: string
meta:
label: { de: "Oben", en: "Top" }
widget: select
hideLabel: true
containerProps:
layout:
size: col-6
choices:
- id: ""
name: { de: "Standard", en: "Default" }
- id: sm
name: { de: "Klein", en: "Small" }
- id: md
name: { de: "Mittel", en: "Medium" }
- id: lg
name: { de: "Groß", en: "Large" }
- name: bottom
type: string
meta:
label: { de: "Unten", en: "Bottom" }
widget: select
hideLabel: true
containerProps:
layout:
size: col-6
choices:
- id: ""
name: { de: "Standard", en: "Default" }
- id: sm
name: { de: "Klein", en: "Small" }
- id: md
name: { de: "Mittel", en: "Medium" }
- id: lg
name: { de: "Groß", en: "Large" }
- name: callToAction
type: object
meta:
label: { de: "Call-to-Action", en: "Call to Action" }
dependsOn:
eval: $parent.type == 'hero'
drillDown: false
subFields:
- name: buttonText
type: string
meta:
label: { de: "Button-Text", en: "Button Text" }
containerProps:
layout:
size: col-4
- name: buttonLink
type: string
meta:
label: { de: "Button-Link", en: "Button Link" }
containerProps:
layout:
size: col-5
- name: buttonTarget
type: string
meta:
label: { de: "Link-Target", en: "Link Target" }
widget: select
containerProps:
layout:
size: col-3
choices:
- id: ""
name: { de: "Gleiches Fenster", en: "Same Window" }
- id: _blank
name: { de: "Neuer Tab", en: "New Tab" }
- name: heroImage
type: object
meta:
label: { de: "Hero-Bild", en: "Hero Image" }
dependsOn:
eval: $parent.type == 'hero'
drillDown: false
subFields:
- name: image
type: string
meta:
label: { de: "Bild", en: "Image" }
widget: foreignMedia
foreign:
collection: medialib
id: _id
subNavigation: 0
- name: text
type: string
meta:
label: { de: "Text", en: "Text" }
dependsOn:
eval: $parent.type == 'richtext'
widget: richtext
inputProps:
rows: 8
- name: featureBoxes
type: object[]
meta:
label: { de: "Feature-Boxen", en: "Feature Boxes" }
dependsOn:
eval: $parent.type == 'features'
drillDown: false
preview: title
subFields:
- name: icon
type: string
meta:
label: { de: "Icon", en: "Icon" }
widget: select
containerProps:
layout:
size: col-4
choices:
- id: lightning
name: { de: "Blitz", en: "Lightning" }
- id: palette
name: { de: "Palette", en: "Palette" }
- id: database
name: { de: "Daten", en: "Data" }
- id: globe
name: { de: "Globus", en: "Globe" }
- id: monitor
name: { de: "Monitor", en: "Monitor" }
- id: flask
name: { de: "Labor", en: "Lab" }
- name: title
type: string
meta:
label: { de: "Titel", en: "Title" }
containerProps:
layout:
size: col-8
- name: text
type: string
meta:
label: { de: "Text", en: "Text" }
inputProps:
multiline: true
rows: 4
- name: imagePosition
type: string
- name: imageRounded
type: string
meta:
label: { de: "Bildposition", en: "Image Position" }
widget: select
dependsOn:
eval: $parent.type == 'richtext'
containerProps:
layout:
size: col-4
choices:
- id: none
name: { de: "Kein Bild", en: "No Image" }
- id: left
name: { de: "Links", en: "Left" }
- id: right
name: { de: "Rechts", en: "Right" }
- name: image
type: string
meta:
label: { de: "Bild", en: "Image" }
dependsOn:
eval: $parent.type == 'richtext'
containerProps:
layout:
size: col-4
widget: foreignMedia
foreign:
collection: medialib
id: _id
subNavigation: 0
- name: accordionItems
type: object[]
meta:
label: { de: "Akkordeon-Elemente", en: "Accordion Items" }
dependsOn:
eval: $parent.type == 'accordion'
drillDown: true
subFields:
- name: question
type: string
meta:
label: { de: "Frage", en: "Question" }
containerProps:
layout:
size: col-8
- name: answer
type: string
meta:
label: { de: "Antwort", en: "Answer" }
widget: richtext
inputProps:
rows: 6
containerProps:
layout:
breakAfter: true
- name: open
type: boolean
- name: imageGallery
type: object
subFields:
- name: images
type: object[]
subFields:
- name: image
type: string
- name: caption
type: string
- name: showCaption
type: boolean
- name: showImageCaption
type: boolean
- name: imageCaption
type: string
meta:
label: { de: "Initial geöffnet", en: "Initially Open" }
containerProps:
layout:
size: col-4
- name: meta
type: object
meta:
widget: containerLessObject
label: { de: "SEO", en: "SEO" }
position: sidebar:seo
subFields:
- name: title
type: string
meta:
label: { de: "Meta-Titel", en: "Meta Title" }
- name: description
type: string
meta:
label: { de: "Meta-Beschreibung", en: "Meta Description" }
inputProps:
multiline: true
rows: 3
- name: keywords
type: string
type: string[]
meta:
label: { de: "Meta-Schlüsselwörter", en: "Meta Keywords" }
+120
View File
@@ -0,0 +1,120 @@
########################################################################
# Media library — reusable uploaded assets referenced via foreignMedia
########################################################################
name: medialib
meta:
label: { de: "Mediathek", en: "Media Library" }
muiIcon: image_multiple
group: media
viewHint:
media:
ai:
targetField: alt
prompt: Beschreibe das Bild kurz und sachlich fuer einen barrierefreien Alt-Text.
image:
maxWidth: 1280
maxHeight: 1280
quality: 0.82
upload:
autoFill:
file: file
preview:
label: title
secondary: description
tertiary: tags
image: file
table:
- file
- title
- tags
subNavigation:
- name: images
label: { de: "Bilder", en: "Images" }
muiIcon: image
filter:
file.type_like: ^image/
- name: documents
label: { de: "Dokumente", en: "Documents" }
muiIcon: file_document
filter:
file.type_like: ^(?!image/|video/|audio/).+
permissions:
public:
methods:
get: true
user:
methods:
get: true
post: true
put: true
delete: true
imageFilter:
xs-webp:
- width: 100
height: 100
fit: true
quality: 60
outputType: webp
s-webp:
- width: 300
quality: 70
outputType: webp
m-webp:
- width: 600
quality: 76
outputType: webp
l-webp:
- width: 1200
quality: 80
outputType: webp
xl-webp:
- width: 2000
quality: 84
outputType: webp
xxl-webp:
- width: 2800
quality: 88
outputType: webp
fields:
- name: file
type: file
meta:
label: { de: "Datei", en: "File" }
widget: file
downscale:
maxWidth: 2048
maxHeight: 2048
- name: title
type: string
meta:
label: { de: "Titel", en: "Title" }
- name: alt
type: object
meta:
label: { de: "Alt-Text", en: "Alt Text" }
subFields:
- name: de
type: string
meta:
label: Deutsch
- name: en
type: string
meta:
label: English
- name: description
type: string
meta:
label: { de: "Beschreibung", en: "Description" }
widget: text
inputProps:
multiline: true
rows: 4
- name: tags
type: string[]
meta:
label: { de: "Tags", en: "Tags" }
widget: chipArray
+63 -14
View File
@@ -6,19 +6,45 @@ name: navigation
meta:
label: { de: "Navigation", en: "Navigation" }
muiIcon: menu
rowIdentTpl: { twig: "{{ type }} ({{ language }})" }
views:
- type: simpleList
mediaQuery: "(max-width: 600px)"
primaryText: type
secondaryText: language
- type: table
columns:
- source: type
filter: true
- source: language
filter: true
group: structure
viewHint:
navigation:
nodesField: elements
preview:
label: name
secondary:
eval: "$this.external && $this.externalUrl ? $this.externalUrl : ($this._lookup?.page ? $this._lookup.page.name + ' (' + $this._lookup.page.path + ')' : '')"
select: [external, externalUrl, page]
declaredTrees:
- label: { de: "Header DE", en: "Header DE" }
singleton:
type: header
language: de
maxLevel: 2
- label: { de: "Header EN", en: "Header EN" }
singleton:
type: header
language: en
maxLevel: 2
- label: { de: "Footer DE", en: "Footer DE" }
singleton:
type: footer
language: de
maxLevel: 1
- label: { de: "Footer EN", en: "Footer EN" }
singleton:
type: footer
language: en
maxLevel: 1
preview:
label: type
secondary: language
table:
- type
- language
sidebar:
- group: settings
label: { de: "Einstellungen", en: "Settings" }
permissions:
public:
@@ -36,15 +62,30 @@ fields:
type: string
meta:
label: { de: "Sprache", en: "Language" }
position: sidebar:settings
widget: select
choices:
- id: de
name: { de: "Deutsch", en: "German" }
- id: en
name: { de: "Englisch", en: "English" }
- name: type
type: string
meta:
label: { de: "Typ", en: "Type" }
helperText: { de: "header oder footer", en: "header or footer" }
position: sidebar:settings
widget: select
choices:
- id: header
name: { de: "Header", en: "Header" }
- id: footer
name: { de: "Footer", en: "Footer" }
- name: elements
type: object[]
meta:
label: { de: "Elemente", en: "Elements" }
preview: name
subFields:
- name: name
type: string
@@ -53,7 +94,11 @@ fields:
- name: page
type: string
meta:
label: { de: "Seite (Content-ID)", en: "Page (Content ID)" }
label: { de: "Seite", en: "Page" }
widget: foreignKey
foreign:
collection: content
id: id
- name: external
type: boolean
meta:
@@ -66,3 +111,7 @@ fields:
type: string
meta:
label: { de: "Anker", en: "Anchor" }
- name: elements
type: object[]
meta:
label: { de: "Unterpunkte", en: "Child Items" }
+2 -16
View File
@@ -6,22 +6,8 @@ name: ssr
meta:
label: { de: "SSR Dummy", en: "ssr dummy" }
muiIcon: server
rowIdentTpl: { twig: "{{ id }}" }
views:
- type: simpleList
mediaQuery: "(max-width: 600px)"
primaryText: id
secondaryText: insertTime
tertiaryText: path
- type: table
columns:
- id
- insertTime
- source: path
filter: true
- source: validUntil
- dependencies
group: system
hide: true
permissions:
public:
+23 -6
View File
@@ -1,15 +1,32 @@
namespace: __NAMESPACE__
namespace: __TIBI_NAMESPACE__
meta:
imageUrl:
eval: "$projectBase + '_/assets/img/admin-pic.jpg'"
injectIntoHead:
# inject font faces (not possible in shadow dom for preview)
eval: |
"<link rel='stylesheet' href='" + $projectBase + "_/assets/fonts/fonts.css?t=" + $project?.updateTime + "'>"
eval: "$projectBase + '_/assets/img/admin-pic.svg'"
i18n:
defaultLanguage: de
languages:
- code: de
label: Deutsch
- code: en
label: English
collectionGroups:
- name: content
label: { de: "Inhalte", en: "Content" }
icon: article
- name: media
label: { de: "Medien", en: "Media" }
icon: image_multiple
- name: structure
label: { de: "Struktur", en: "Structure" }
icon: account_tree
- name: system
label: { de: "System", en: "System" }
icon: settings
collections:
- !include collections/content.yml
- !include collections/medialib.yml
- !include collections/navigation.yml
- !include collections/ssr.yml