mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2025-06-29 08:12:01 +02:00
Compare commits
13 Commits
00e0096f8a
...
3acf7d36a4
Author | SHA1 | Date | |
---|---|---|---|
3acf7d36a4 | |||
2e89e8c31e | |||
ef0d98cdd1 | |||
9f143e39b4 | |||
8ad3ca54cc | |||
9ff356ba70 | |||
c01e9125aa | |||
c24e35c4e8 | |||
b74ff2da78 | |||
066f10edfc | |||
abdaf54147 | |||
ac0b1e9a14 | |||
f9d5a90f94 |
622
app/config.yaml
622
app/config.yaml
@ -1,137 +1,172 @@
|
|||||||
---
|
---
|
||||||
accounts:
|
accounts:
|
||||||
name: Accounts
|
name: Online Accounts
|
||||||
description: My Online Accounts
|
description: Discover my online presence.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-users
|
class: fa-solid fa-users
|
||||||
subitems:
|
children:
|
||||||
- name: Publications
|
- name: Channels
|
||||||
description: My Publications
|
description: Platforms where I share content.
|
||||||
icon:
|
icon:
|
||||||
class: fas fa-newspaper
|
class: fas fa-newspaper
|
||||||
subitems:
|
children:
|
||||||
- name: Microblog
|
- name: Microblogs
|
||||||
description: Read my microblogs
|
description: Stay updated with my microblog posts.
|
||||||
icon:
|
icon:
|
||||||
|
class: fa-solid fa-pen-nib
|
||||||
|
children:
|
||||||
|
- name: Mastodon
|
||||||
|
description: Follow my updates on Mastodon.
|
||||||
|
icon:
|
||||||
class: fa-brands fa-mastodon
|
class: fa-brands fa-mastodon
|
||||||
url: https://microblog.veen.world/@kevinveenbirkenbach
|
url: https://microblog.veen.world/@kevinveenbirkenbach
|
||||||
|
identifier: "@kevinveenbirkenbach@microblog.veen.world"
|
||||||
|
- name: Twitter
|
||||||
|
description: Follow me on Twitter (limited use).
|
||||||
|
icon:
|
||||||
|
class: fa-brands fa-twitter
|
||||||
|
url: https://s.veen.world/twitter
|
||||||
|
identifier: kevinbirkenbach
|
||||||
|
warning: I rarely use X/Twitter and recommend alternative platforms like Mastodon.
|
||||||
|
alternatives:
|
||||||
|
- link: accounts.channels.microblogs.mastodon
|
||||||
|
- name: Bluesky
|
||||||
|
description: Follow me on Bluesky (coming soon).
|
||||||
|
icon:
|
||||||
|
class: fa-brands fa-bluesky
|
||||||
|
alternatives:
|
||||||
|
- link: accounts.channels.microblogs.mastodon
|
||||||
|
|
||||||
- name: Pictures
|
- name: Pictures
|
||||||
|
description: View my photography.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-images
|
class: fa-solid fa-images
|
||||||
subitems:
|
children:
|
||||||
- name: Pixelfed
|
- name: Pixelfed
|
||||||
description: View my photo gallery
|
description: Explore my photo gallery on Pixelfed.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-camera
|
class: fa-solid fa-camera
|
||||||
url: https://s.veen.world/pictures
|
url: https://s.veen.world/pictures
|
||||||
- name: Instagram
|
- name: Instagram
|
||||||
description: Follow me on Instagram
|
description: Follow me on Instagram.
|
||||||
icon:
|
icon:
|
||||||
class: fa-brands fa-instagram
|
class: fa-brands fa-instagram
|
||||||
url: https://www.instagram.com/kevinveenbirkenbach/
|
url: https://www.instagram.com/kevinveenbirkenbach/
|
||||||
identifier: kevinveenbirkenbach
|
identifier: kevinveenbirkenbach
|
||||||
warning: Using software and platforms from the Meta corporation (e.g., Facebook, Instagram, WhatsApp) may compromise your data privacy and digital freedom due to centralized control, extensive data collection practices, and inconsistent moderation policies. These platforms often fail to adequately address harmful content, misinformation, and abuse.
|
warning: Platforms by Meta (e.g., Instagram, Facebook) may compromise your data privacy. Consider using decentralized alternatives.
|
||||||
|
alternatives:
|
||||||
|
- link: accounts.channels.pictures.pixelfed
|
||||||
|
|
||||||
- name: Videos
|
- name: Videos
|
||||||
description: Watch my videos
|
description: Watch my video content.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-video
|
class: fa-solid fa-video
|
||||||
url: https://s.veen.world/videos
|
children:
|
||||||
|
- name: Peertube
|
||||||
|
description: Discover my videos on Peertube.
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-video
|
||||||
|
url: https://s.veen.world/videos
|
||||||
|
- name: YouTube
|
||||||
|
description: Follow me on YouTube (inactive).
|
||||||
|
icon:
|
||||||
|
class: fa-brands fa-youtube
|
||||||
|
url: https://s.veen.world/youtube
|
||||||
|
warning: I no longer publish videos on YouTube. Please visit my Peertube channel instead.
|
||||||
|
alternatives:
|
||||||
|
- link: accounts.channels.videos.peertube
|
||||||
|
|
||||||
- name: Blog
|
- name: Blog
|
||||||
description: Read my blog
|
description: Read my articles and stories.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-blog
|
class: fa-solid fa-blog
|
||||||
url: https://blog.veen.world
|
url: https://blog.veen.world
|
||||||
|
|
||||||
- name: Code
|
- name: Code
|
||||||
|
description: Access my coding projects.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-laptop-code
|
class: fa-solid fa-laptop-code
|
||||||
description: Check out my Code
|
children:
|
||||||
subitems:
|
- name: GitHub
|
||||||
- name: Github
|
description: View my GitHub repositories.
|
||||||
description: View my GitHub profile
|
|
||||||
icon:
|
icon:
|
||||||
class: bi bi-github
|
class: bi bi-github
|
||||||
url: https://github.com/kevinveenbirkenbach
|
url: https://github.com/kevinveenbirkenbach
|
||||||
|
|
||||||
- name: Gitea
|
- name: Gitea
|
||||||
description: Explore my code repositories
|
description: Explore my self-hosted repositories.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-code
|
class: fa-solid fa-code
|
||||||
url: https://git.veen.world/kevinveenbirkenbach
|
url: https://git.veen.world/kevinveenbirkenbach
|
||||||
|
|
||||||
- name: Social Media
|
- name: Social Media
|
||||||
description: Social and developer networks
|
description: Social and developer platforms.
|
||||||
icon:
|
icon:
|
||||||
class: fa-brands fa-meta
|
class: fa-brands fa-meta
|
||||||
url:
|
children:
|
||||||
subitems:
|
|
||||||
- name: Facebook
|
- name: Facebook
|
||||||
description: Like my Facebook page
|
description: Visit my Facebook page.
|
||||||
icon:
|
icon:
|
||||||
class: fa-brands fa-facebook
|
class: fa-brands fa-facebook
|
||||||
url: https://www.facebook.com/kevinveenbirkenbach
|
url: https://www.facebook.com/kevinveenbirkenbach
|
||||||
|
|
||||||
- link: navigation.header.contact.messenger
|
- link: navigation.header.contact.messenger
|
||||||
- name: Carreer Profiles
|
|
||||||
|
- name: Career Profiles
|
||||||
|
description: Professional networking profiles.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-user-tie
|
class: fa-solid fa-user-tie
|
||||||
subitems:
|
children:
|
||||||
- name: XING
|
- name: XING
|
||||||
description: Visit my XING profile
|
description: View my XING profile.
|
||||||
icon:
|
icon:
|
||||||
class: bi bi-building
|
class: bi bi-building
|
||||||
url: https://www.xing.com/profile/Kevin_VeenBirkenbach
|
url: https://www.xing.com/profile/Kevin_VeenBirkenbach
|
||||||
|
|
||||||
- name: LinkedIn
|
- name: LinkedIn
|
||||||
description: Connect on LinkedIn
|
description: Connect with me on LinkedIn.
|
||||||
icon:
|
icon:
|
||||||
class: bi bi-linkedin
|
class: bi bi-linkedin
|
||||||
url: https://www.linkedin.com/in/kevinveenbirkenbach
|
url: https://www.linkedin.com/in/kevinveenbirkenbach
|
||||||
|
|
||||||
- name: Sports
|
- name: Sports
|
||||||
description: My sport activities
|
description: My sports activities and logs.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-running
|
class: fa-solid fa-running
|
||||||
url:
|
children:
|
||||||
subitems:
|
|
||||||
- name: Garmin
|
- name: Garmin
|
||||||
description: My Garmin activities
|
description: Explore my Garmin activity records.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-person-running
|
class: fa-solid fa-person-running
|
||||||
url: https://s.veen.world/garmin
|
url: https://s.veen.world/garmin
|
||||||
|
|
||||||
- name: Eversports
|
- name: Eversports
|
||||||
description: My Eversports sessions
|
description: View my Eversports sessions.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-dumbbell
|
class: fa-solid fa-dumbbell
|
||||||
url: https://s.veen.world/eversports
|
url: https://s.veen.world/eversports
|
||||||
|
|
||||||
- name: Duolingo
|
- name: Duolingo
|
||||||
description: Learn with me on Duolingo
|
description: Join me in language learning.
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-language
|
class: fa-solid fa-language
|
||||||
url: https://www.duolingo.com/profile/kevinbirkenbach
|
url: https://www.duolingo.com/profile/kevinbirkenbach
|
||||||
|
|
||||||
- name: Spotify
|
- name: Spotify
|
||||||
description: Listen to my playlists
|
description: Listen to my playlists.
|
||||||
icon:
|
icon:
|
||||||
class: fa-brands fa-spotify
|
class: fa-brands fa-spotify
|
||||||
url: https://open.spotify.com/user/31vebfzbjf3p7oualis76qfpr5ty
|
url: https://open.spotify.com/user/31vebfzbjf3p7oualis76qfpr5ty
|
||||||
|
|
||||||
- name: Patreon
|
- name: Patreon
|
||||||
description: Support me on Patreon
|
description: Support my work on Patreon.
|
||||||
icon:
|
icon:
|
||||||
class: fa-brands fa-patreon
|
class: fa-brands fa-patreon
|
||||||
url: https://patreon.com/kevinveenbirkenbach
|
url: https://patreon.com/kevinveenbirkenbach
|
||||||
|
|
||||||
- name: Discourse
|
- name: Discourse
|
||||||
description: Follow me on Discourse
|
description: Join discussions on my forum.
|
||||||
icon:
|
icon:
|
||||||
class: fa-brands fa-discourse
|
class: fa-brands fa-discourse
|
||||||
url: https://forum.veen.world/u/kevinveenbirkenbach
|
url: https://forum.veen.world/u/kevinveenbirkenbach
|
||||||
|
|
||||||
|
|
||||||
cards:
|
cards:
|
||||||
- icon:
|
- icon:
|
||||||
source: https://cloud.veen.world/s/logo_agile_coach_512x512/download
|
source: https://cloud.veen.world/s/logo_agile_coach_512x512/download
|
||||||
@ -251,262 +286,279 @@ company:
|
|||||||
imprint_url: https://s.veen.world/imprint
|
imprint_url: https://s.veen.world/imprint
|
||||||
navigation:
|
navigation:
|
||||||
header:
|
header:
|
||||||
- name: Contact
|
children:
|
||||||
description: Get in touch
|
- link: accounts.channels.children
|
||||||
icon:
|
- name: Contact
|
||||||
class: fa-solid fa-envelope
|
description: Get in touch
|
||||||
subitems:
|
|
||||||
- name: Email
|
|
||||||
description: Send me an email
|
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-envelope
|
class: fa-solid fa-envelope
|
||||||
subitems:
|
children:
|
||||||
- name: Email
|
- name: Email
|
||||||
description: Send me an email
|
description: Send me an email
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-envelope
|
class: fa-solid fa-envelope
|
||||||
url: mailto:kevin@veen.world
|
children:
|
||||||
identifier: kevin@veen.world
|
- name: Email
|
||||||
alternatives:
|
description: Send me an email
|
||||||
- link: navigation.header.contact.messenger.matrix
|
icon:
|
||||||
- name: Encrypted Email (PGP)
|
class: fa-solid fa-envelope
|
||||||
description: Download my PGP key
|
url: mailto:kevin@veen.world
|
||||||
|
identifier: kevin@veen.world
|
||||||
|
alternatives:
|
||||||
|
- link: navigation.header.contact.messenger.matrix
|
||||||
|
- name: Encrypted Email (PGP)
|
||||||
|
description: Download my PGP key
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-key
|
||||||
|
url: https://s.veen.world/pgp
|
||||||
|
identifier: kevin@veen.world
|
||||||
|
info: |
|
||||||
|
#### Why Use PGP?
|
||||||
|
PGP ensures your email content stays private, protecting against surveillance, data breaches, and unauthorized access.
|
||||||
|
|
||||||
|
#### Protect Your Privacy
|
||||||
|
In an age of mass data collection, PGP empowers you to communicate securely and assert control over your information. For insights on protecting your digital rights, visit the [Electronic Frontier Foundation (EFF)](https://www.eff.org/).
|
||||||
|
|
||||||
|
#### Build Trust
|
||||||
|
Encrypting emails demonstrates a commitment to privacy and security, fostering trust in professional and personal communication.
|
||||||
|
|
||||||
|
#### Stand for Security
|
||||||
|
Using PGP is more than a tool—it's a statement about valuing freedom, privacy, and the security of digital communication. Explore the principles of secure communication with [privacy guides](https://privacyguides.org/).
|
||||||
|
- name: Mobile
|
||||||
|
description: Call me
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-key
|
class: fa-solid fa-phone
|
||||||
url: https://s.veen.world/pgp
|
url: "tel:+491781798023"
|
||||||
identifier: kevin@veen.world
|
|
||||||
info: |
|
|
||||||
#### Why Use PGP?
|
|
||||||
PGP ensures your email content stays private, protecting against surveillance, data breaches, and unauthorized access.
|
|
||||||
|
|
||||||
#### Protect Your Privacy
|
|
||||||
In an age of mass data collection, PGP empowers you to communicate securely and assert control over your information. For insights on protecting your digital rights, visit the [Electronic Frontier Foundation (EFF)](https://www.eff.org/).
|
|
||||||
|
|
||||||
#### Build Trust
|
|
||||||
Encrypting emails demonstrates a commitment to privacy and security, fostering trust in professional and personal communication.
|
|
||||||
|
|
||||||
#### Stand for Security
|
|
||||||
Using PGP is more than a tool—it's a statement about valuing freedom, privacy, and the security of digital communication. Explore the principles of secure communication with [privacy guides](https://privacyguides.org/).
|
|
||||||
- name: Mobile
|
|
||||||
description: Call me
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-phone
|
|
||||||
url: "tel:+491781798023"
|
|
||||||
identifier: "+491781798023"
|
|
||||||
target: _top
|
|
||||||
- name: Messenger
|
|
||||||
description: Social and developer networks
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-comments
|
|
||||||
subitems:
|
|
||||||
- name: Matrix
|
|
||||||
description: Chat with me on Matrix
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-cubes
|
|
||||||
identifier: "@kevinveenbirkenbach:veen.world"
|
|
||||||
info: |
|
|
||||||
#### Why Use Matrix?
|
|
||||||
Matrix is a secure, decentralized communication platform that ensures privacy and control over your data. Learn more about [Matrix](https://matrix.org/).
|
|
||||||
|
|
||||||
#### Privacy and Security
|
|
||||||
End-to-end encryption keeps your conversations private and secure.
|
|
||||||
|
|
||||||
#### Decentralized and Open
|
|
||||||
Matrix's federated network means you can host your own server or use any provider while staying connected.
|
|
||||||
|
|
||||||
#### A Movement for Digital Freedom
|
|
||||||
By using Matrix, you support open, transparent, and secure communication.
|
|
||||||
- name: Signal
|
|
||||||
description: Message me on Signal
|
|
||||||
icon:
|
|
||||||
class: fa-brands fa-signal-messenger
|
|
||||||
identifier: "+491781798023"
|
identifier: "+491781798023"
|
||||||
warning: Signal is not hosted by me!
|
target: _top
|
||||||
alternatives:
|
- name: Messenger
|
||||||
- link: navigation.header.contact.messenger.matrix
|
description: Social and developer networks
|
||||||
- name: Telegram
|
|
||||||
description: Message me on Telegram
|
|
||||||
icon:
|
icon:
|
||||||
class: fa-brands fa-telegram
|
class: fa-solid fa-comments
|
||||||
target: _blank
|
children:
|
||||||
url: https://t.me/kevinveenbirkenbach
|
- name: Matrix
|
||||||
identifier: kevinveenbirkenbach
|
description: Chat with me on Matrix
|
||||||
warning: Telegram is not hosted by me!
|
icon:
|
||||||
alternatives:
|
class: fa-solid fa-cubes
|
||||||
- link: navigation.header.contact.messenger.matrix
|
identifier: "@kevinveenbirkenbach:veen.world"
|
||||||
- name: WhatsApp
|
info: |
|
||||||
description: Chat with me on WhatsApp
|
#### Why Use Matrix?
|
||||||
icon:
|
Matrix is a secure, decentralized communication platform that ensures privacy and control over your data. Learn more about [Matrix](https://matrix.org/).
|
||||||
class: fa-brands fa-whatsapp
|
|
||||||
url: https://wa.me/491781798023
|
|
||||||
identifier: "+491781798023"
|
|
||||||
info: Consider using decentralized and privacy-respecting alternatives to maintain control over your data, improve security, and foster healthier online interactions.
|
|
||||||
alternatives:
|
|
||||||
- link: navigation.header.contact.messenger.matrix
|
|
||||||
- link: navigation.header.contact.messenger.signal
|
|
||||||
- link: navigation.header.contact.messenger.telegram
|
|
||||||
|
|
||||||
|
#### Privacy and Security
|
||||||
|
End-to-end encryption keeps your conversations private and secure.
|
||||||
|
|
||||||
|
#### Decentralized and Open
|
||||||
|
Matrix's federated network means you can host your own server or use any provider while staying connected.
|
||||||
|
|
||||||
|
#### A Movement for Digital Freedom
|
||||||
|
By using Matrix, you support open, transparent, and secure communication.
|
||||||
|
- name: Signal
|
||||||
|
description: Message me on Signal
|
||||||
|
icon:
|
||||||
|
class: fa-brands fa-signal-messenger
|
||||||
|
identifier: "+491781798023"
|
||||||
|
warning: Signal is not hosted by me!
|
||||||
|
alternatives:
|
||||||
|
- link: navigation.header.contact.messenger.matrix
|
||||||
|
- name: Telegram
|
||||||
|
description: Message me on Telegram
|
||||||
|
icon:
|
||||||
|
class: fa-brands fa-telegram
|
||||||
|
target: _blank
|
||||||
|
url: https://t.me/kevinveenbirkenbach
|
||||||
|
identifier: kevinveenbirkenbach
|
||||||
|
warning: Telegram is not hosted by me!
|
||||||
|
alternatives:
|
||||||
|
- link: navigation.header.contact.messenger.matrix
|
||||||
|
- name: WhatsApp
|
||||||
|
description: Chat with me on WhatsApp
|
||||||
|
icon:
|
||||||
|
class: fa-brands fa-whatsapp
|
||||||
|
url: https://wa.me/491781798023
|
||||||
|
identifier: "+491781798023"
|
||||||
|
info: Consider using decentralized and privacy-respecting alternatives to maintain control over your data, improve security, and foster healthier online interactions.
|
||||||
|
alternatives:
|
||||||
|
- link: navigation.header.contact.messenger.matrix
|
||||||
|
- link: navigation.header.contact.messenger.signal
|
||||||
|
- link: navigation.header.contact.messenger.telegram
|
||||||
footer:
|
footer:
|
||||||
- link: accounts
|
children:
|
||||||
- name: Solution Hub
|
- link: accounts
|
||||||
description: Curated collection of self hosted tools
|
- name: Solution Hub
|
||||||
icon:
|
description: Curated collection of self hosted tools
|
||||||
class: fa-solid fa-network-wired
|
|
||||||
url:
|
|
||||||
subitems:
|
|
||||||
- name: Community
|
|
||||||
description: Tools to manage the community
|
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-users
|
class: fa-solid fa-network-wired
|
||||||
subitems:
|
url:
|
||||||
- name: Forum
|
children:
|
||||||
description: Join the discussion
|
- name: Community
|
||||||
|
description: Tools to manage the community
|
||||||
icon:
|
icon:
|
||||||
class: fa-brands fa-discourse
|
class: fa-solid fa-users
|
||||||
url: https://forum.veen.world/
|
children:
|
||||||
- name: Learning Platform
|
- name: Forum
|
||||||
description: Learn with my academy
|
description: Join the discussion
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-graduation-cap
|
class: fa-brands fa-discourse
|
||||||
url: https://academy.veen.world/
|
url: https://forum.veen.world/
|
||||||
- name: Newsletter
|
- name: Learning Platform
|
||||||
description: Subscribe to my newsletter
|
description: Learn with my academy
|
||||||
icon:
|
|
||||||
class: fa-solid fa-envelope-open-text
|
|
||||||
url: https://newsletter.veen.world/subscription/form
|
|
||||||
- name: Project Management
|
|
||||||
description: Project Management Tools
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-chart-line
|
|
||||||
subitems:
|
|
||||||
- name: Open Project
|
|
||||||
description: Explore my projects
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-tasks
|
|
||||||
url: https://project.veen.world/
|
|
||||||
|
|
||||||
- name: Taiga
|
|
||||||
description: View my Kanban board
|
|
||||||
icon:
|
|
||||||
class: bi bi-clipboard2-check-fill
|
|
||||||
url: https://kanban.veen.world/
|
|
||||||
|
|
||||||
- name: Communication
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-comments
|
|
||||||
subitems:
|
|
||||||
- name: Elements
|
|
||||||
description: Chat with me
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-comment
|
|
||||||
url: https://element.veen.world/
|
|
||||||
|
|
||||||
- name: Big Blue Button
|
|
||||||
description: Join live events
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-video
|
|
||||||
url: https://meet.veen.world/
|
|
||||||
|
|
||||||
- name: Mailu
|
|
||||||
description: Send me a mail
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-envelope
|
|
||||||
url: https://mail.veen.world/
|
|
||||||
- name: Tools
|
|
||||||
icon:
|
|
||||||
class: fas fa-tools
|
|
||||||
subitems:
|
|
||||||
- name: Matomo
|
|
||||||
description: Analyze with Matomo
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-chart-simple
|
|
||||||
url: https://matomo.veen.world/
|
|
||||||
|
|
||||||
- name: Baserow
|
|
||||||
description: Organize with Baserow
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-table
|
|
||||||
url: https://baserow.veen.world/
|
|
||||||
- name: Yourls
|
|
||||||
description: Find my curated links
|
|
||||||
icon:
|
|
||||||
class: bi bi-link
|
|
||||||
url: https://s.veen.world/admin/
|
|
||||||
|
|
||||||
- name: Nextcloud
|
|
||||||
description: Access my cloud storage
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-cloud
|
|
||||||
url: https://cloud.veen.world/
|
|
||||||
|
|
||||||
- name: About
|
|
||||||
description: All information about me
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-user
|
|
||||||
subitems:
|
|
||||||
- name: Logbooks
|
|
||||||
description: Access my personal logbooks (diving, flying, sailing)
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-book
|
|
||||||
subitems:
|
|
||||||
- name: Skydiver
|
|
||||||
description: View my skydiving logs
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-parachute-box
|
|
||||||
url: https://s.veen.world/skydiverlog
|
|
||||||
|
|
||||||
- name: Skipper
|
|
||||||
description: See my sailing records
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-sailboat
|
|
||||||
url: https://s.veen.world/meilenbuch
|
|
||||||
|
|
||||||
- name: Diver
|
|
||||||
description: Check my diving logs
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-fish
|
|
||||||
url: https://s.veen.world/diverlog
|
|
||||||
|
|
||||||
- name: Pilot
|
|
||||||
description: Review my flight logs
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-plane
|
|
||||||
url: https://s.veen.world/pilotlog
|
|
||||||
|
|
||||||
- name: Nature
|
|
||||||
description: Explore my nature logs
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-tree
|
|
||||||
url: https://s.veen.world/naturejournal
|
|
||||||
- name: Vita
|
|
||||||
description: View my CV
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-file-lines
|
|
||||||
url: https://s.veen.world/lebenslauf
|
|
||||||
- name: Credentials
|
|
||||||
description: Access my certifications, degrees, and professional records
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-id-card
|
|
||||||
subitems:
|
|
||||||
- name: Degrees
|
|
||||||
description: View my academic degrees
|
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-graduation-cap
|
class: fa-solid fa-graduation-cap
|
||||||
url: https://s.veen.world/degrees
|
url: https://academy.veen.world/
|
||||||
- name: Certificates
|
- name: Newsletter
|
||||||
description: View my training and professional development records
|
description: Subscribe to my newsletter
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-certificate
|
class: fa-solid fa-envelope-open-text
|
||||||
url: https://s.veen.world/certificates
|
url: https://newsletter.veen.world/subscription/form
|
||||||
- name: Certifications
|
- name: Project Management
|
||||||
description: Browse all my certifications
|
description: Project Management Tools
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-chart-line
|
||||||
|
children:
|
||||||
|
- name: Open Project
|
||||||
|
description: Explore my projects
|
||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-scroll
|
class: fa-solid fa-tasks
|
||||||
url: https://s.veen.world/certifications
|
url: https://project.veen.world/
|
||||||
- link: accounts
|
|
||||||
|
- name: Taiga
|
||||||
|
description: View my Kanban board
|
||||||
|
icon:
|
||||||
|
class: bi bi-clipboard2-check-fill
|
||||||
|
url: https://kanban.veen.world/
|
||||||
|
|
||||||
|
- name: Communication
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-comments
|
||||||
|
children:
|
||||||
|
- name: Elements
|
||||||
|
description: Chat with me
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-comment
|
||||||
|
url: https://element.veen.world/
|
||||||
|
|
||||||
|
- name: Big Blue Button
|
||||||
|
description: Join live events
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-video
|
||||||
|
url: https://meet.veen.world/
|
||||||
|
|
||||||
|
- name: Mailu
|
||||||
|
description: Send me a mail
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-envelope
|
||||||
|
url: https://mail.veen.world/
|
||||||
|
- name: Tools
|
||||||
|
icon:
|
||||||
|
class: fas fa-tools
|
||||||
|
children:
|
||||||
|
- name: Matomo
|
||||||
|
description: Analyze with Matomo
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-chart-simple
|
||||||
|
url: https://matomo.veen.world/
|
||||||
|
|
||||||
|
- name: Baserow
|
||||||
|
description: Organize with Baserow
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-table
|
||||||
|
url: https://baserow.veen.world/
|
||||||
|
- name: Yourls
|
||||||
|
description: Find my curated links
|
||||||
|
icon:
|
||||||
|
class: bi bi-link
|
||||||
|
url: https://s.veen.world/admin/
|
||||||
|
|
||||||
|
- name: Nextcloud
|
||||||
|
description: Access my cloud storage
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-cloud
|
||||||
|
url: https://cloud.veen.world/
|
||||||
|
|
||||||
|
- name: About
|
||||||
|
description: All information about me
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-user
|
||||||
|
children:
|
||||||
|
- name: Logbooks
|
||||||
|
description: Access my personal logbooks (diving, flying, sailing)
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-book
|
||||||
|
children:
|
||||||
|
- name: Skydiver
|
||||||
|
description: View my skydiving logs
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-parachute-box
|
||||||
|
url: https://s.veen.world/skydiverlog
|
||||||
|
|
||||||
|
- name: Skipper
|
||||||
|
description: See my sailing records
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-sailboat
|
||||||
|
url: https://s.veen.world/meilenbuch
|
||||||
|
|
||||||
|
- name: Diver
|
||||||
|
description: Check my diving logs
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-fish
|
||||||
|
url: https://s.veen.world/diverlog
|
||||||
|
|
||||||
|
- name: Pilot
|
||||||
|
description: Review my flight logs
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-plane
|
||||||
|
url: https://s.veen.world/pilotlog
|
||||||
|
|
||||||
|
- name: Nature
|
||||||
|
description: Explore my nature logs
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-tree
|
||||||
|
url: https://s.veen.world/naturejournal
|
||||||
|
- name: Vita
|
||||||
|
description: View my CV
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-file-lines
|
||||||
|
url: https://s.veen.world/lebenslauf
|
||||||
|
- name: Languages
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-language
|
||||||
|
children:
|
||||||
|
- link: accounts.duolingo
|
||||||
|
- name: Languages Credentials
|
||||||
|
description: Check out which languages I speak
|
||||||
|
url: https://s.veen.world/languages
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-language
|
||||||
|
- name: Credentials
|
||||||
|
description: Access my certifications, degrees, and professional records
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-id-card
|
||||||
|
children:
|
||||||
|
- name: Degrees
|
||||||
|
description: View my academic degrees
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-graduation-cap
|
||||||
|
url: https://s.veen.world/degrees
|
||||||
|
- name: Certificates
|
||||||
|
description: View my training and professional development records
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-certificate
|
||||||
|
url: https://s.veen.world/certificates
|
||||||
|
- name: Certifications
|
||||||
|
description: Browse all my certifications
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-scroll
|
||||||
|
url: https://s.veen.world/certifications
|
||||||
|
- name: Skill Matrix
|
||||||
|
description: Checkout my skills
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-layer-group
|
||||||
|
url: https://s.veen.world/skillmatrix
|
||||||
|
- link: accounts
|
||||||
|
- name: Imprint
|
||||||
|
description: Check out the imprint information
|
||||||
|
icon:
|
||||||
|
class: fa-solid fa-scale-balanced
|
||||||
|
url: https://s.veen.world/imprint
|
||||||
|
|
||||||
- name: Imprint
|
|
||||||
description: Check out the imprint information
|
|
||||||
icon:
|
|
||||||
class: fa-solid fa-scale-balanced
|
|
||||||
url: https://s.veen.world/imprint
|
|
||||||
|
@ -23,13 +23,17 @@ a {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Subtle shadow effect */
|
||||||
|
.navbar, .card, .dropdown-menu{
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
/* Card styles */
|
/* Card styles */
|
||||||
.navbar, .card {
|
.navbar, .card {
|
||||||
flex: 1; /* Ensures cards fill the height of their container */
|
flex: 1; /* Ensures cards fill the height of their container */
|
||||||
border-radius: 5px; /* Rounded corners */
|
border-radius: 5px; /* Rounded corners */
|
||||||
border: 1px solid #ccc; /* Optional border color */
|
border: 1px solid #ccc; /* Optional border color */
|
||||||
padding: 10px; /* Inner spacing */
|
padding: 10px; /* Inner spacing */
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); /* Subtle shadow effect */
|
|
||||||
color: #000000 !important;
|
color: #000000 !important;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
@ -3,34 +3,13 @@
|
|||||||
display: none;
|
display: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transition: opacity 0.3s ease, visibility 0.3s ease;
|
width: max-content !important; /* Passt die Breite an das breiteste Item an */
|
||||||
}
|
box-sizing: border-box; /* Berücksichtigt Innenabstand und Rahmen */
|
||||||
|
}
|
||||||
|
|
||||||
/* Dropdown-Menü beim Hover anzeigen */
|
/* Positionierung von Submenüs */
|
||||||
.nav-item.dropdown:hover > .dropdown-menu,
|
.dropdown-submenu > .dropdown-menu {
|
||||||
.dropdown-submenu:hover > .dropdown-menu {
|
|
||||||
display: block;
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dropdown-Menü bei der Klasse "open" anzeigen */
|
|
||||||
.nav-item.dropdown.open > .dropdown-menu,
|
|
||||||
.dropdown-submenu.open > .dropdown-menu {
|
|
||||||
display: block;
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Positionierung von Submenüs */
|
|
||||||
.dropdown-submenu > .dropdown-menu {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
transition: opacity 0.3s ease, visibility 0.3s ease;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 100%; /* Rechts ausklappen */
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-submenu.open > .dropdown-menu {
|
|
||||||
display: block;
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
@ -2,57 +2,47 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const menuItems = document.querySelectorAll('.nav-item.dropdown');
|
const menuItems = document.querySelectorAll('.nav-item.dropdown');
|
||||||
const subMenuItems = document.querySelectorAll('.dropdown-submenu');
|
const subMenuItems = document.querySelectorAll('.dropdown-submenu');
|
||||||
|
|
||||||
menuItems.forEach(item => {
|
function addMenuEventListeners(items, isTopLevel) {
|
||||||
let timeout;
|
items.forEach(item => {
|
||||||
|
let timeout;
|
||||||
|
|
||||||
// Öffnen beim Hovern
|
function onMouseEnter() {
|
||||||
item.addEventListener('mouseenter', () => {
|
clearTimeout(timeout);
|
||||||
clearTimeout(timeout);
|
openMenu(item, isTopLevel);
|
||||||
openMenu(item, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verzögertes Schließen beim Verlassen
|
|
||||||
item.addEventListener('mouseleave', () => {
|
|
||||||
timeout = setTimeout(() => closeMenu(item), 500);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Öffnen und Position anpassen beim Klicken
|
|
||||||
item.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault(); // Verhindert die Standardaktion
|
|
||||||
e.stopPropagation(); // Verhindert das Schließen von Menüs bei Klick
|
|
||||||
if (item.classList.contains('open')) {
|
|
||||||
closeMenu(item);
|
|
||||||
} else {
|
|
||||||
openMenu(item, true);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
subMenuItems.forEach(item => {
|
function onMouseLeave() {
|
||||||
let timeout;
|
timeout = setTimeout(() => {
|
||||||
|
closeMenu(item);
|
||||||
// Öffnen beim Hovern
|
}, 500);
|
||||||
item.addEventListener('mouseenter', () => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
openMenu(item, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verzögertes Schließen beim Verlassen
|
|
||||||
item.addEventListener('mouseleave', () => {
|
|
||||||
timeout = setTimeout(() => closeMenu(item), 500);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Öffnen und Position anpassen beim Klicken
|
|
||||||
item.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault(); // Verhindert die Standardaktion
|
|
||||||
e.stopPropagation(); // Verhindert das Schließen von Menüs bei Klick
|
|
||||||
if (item.classList.contains('open')) {
|
|
||||||
closeMenu(item);
|
|
||||||
} else {
|
|
||||||
openMenu(item, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Öffnen beim Hovern
|
||||||
|
item.addEventListener('mouseenter', onMouseEnter);
|
||||||
|
|
||||||
|
// Verzögertes Schließen beim Verlassen
|
||||||
|
item.addEventListener('mouseleave', onMouseLeave);
|
||||||
|
|
||||||
|
// Öffnen und Position anpassen beim Klicken
|
||||||
|
item.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation(); // Verhindert das Schließen von Menüs bei Klick
|
||||||
|
if (item.classList.contains('open')) {
|
||||||
|
closeMenu(item);
|
||||||
|
} else {
|
||||||
|
openMenu(item, isTopLevel);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
function addAllMenuEventListeners() {
|
||||||
|
const updatedMenuItems = document.querySelectorAll('.nav-item.dropdown');
|
||||||
|
const updatedSubMenuItems = document.querySelectorAll('.dropdown-submenu');
|
||||||
|
addMenuEventListeners(updatedMenuItems, true);
|
||||||
|
addMenuEventListeners(updatedSubMenuItems, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAllMenuEventListeners();
|
||||||
|
|
||||||
// Globale Klick-Listener, um Menüs zu schließen, wenn außerhalb geklickt wird
|
// Globale Klick-Listener, um Menüs zu schließen, wenn außerhalb geklickt wird
|
||||||
document.addEventListener('click', () => {
|
document.addEventListener('click', () => {
|
||||||
@ -63,10 +53,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
item.classList.add('open');
|
item.classList.add('open');
|
||||||
const submenu = item.querySelector('.dropdown-menu');
|
const submenu = item.querySelector('.dropdown-menu');
|
||||||
if (submenu) {
|
if (submenu) {
|
||||||
adjustMenuPosition(submenu, item, isTopLevel);
|
|
||||||
submenu.style.display = 'block';
|
submenu.style.display = 'block';
|
||||||
submenu.style.opacity = '1';
|
submenu.style.opacity = '1';
|
||||||
submenu.style.visibility = 'visible';
|
submenu.style.visibility = 'visible';
|
||||||
|
adjustMenuPosition(submenu, item, isTopLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +89,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (isTopLevel) {
|
if (isTopLevel) {
|
||||||
// Top-Level-Menüs öffnen nur nach oben oder unten
|
// Top-Level-Menüs öffnen nur nach oben oder unten
|
||||||
if (spaceBelow < rect.height && spaceAbove > rect.height) {
|
if (spaceBelow < rect.height && spaceAbove > rect.height) {
|
||||||
submenu.style.bottom = '100%';
|
submenu.style.bottom = `${window.innerHeight - parentRect.bottom - parentRect.height}px`;
|
||||||
submenu.style.top = 'auto';
|
submenu.style.top = 'auto';
|
||||||
} else {
|
} else {
|
||||||
submenu.style.top = `${parentRect.height}px`;
|
submenu.style.top = `${parentRect.height}px`;
|
||||||
@ -111,9 +101,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
submenu.style.left = prefersRight ? '100%' : 'auto';
|
submenu.style.left = prefersRight ? '100%' : 'auto';
|
||||||
submenu.style.right = prefersRight ? 'auto' : '100%';
|
submenu.style.right = prefersRight ? 'auto' : '100%';
|
||||||
|
|
||||||
const prefersBelow = spaceBelow >= spaceAbove;
|
// Öffnen nach oben, wenn unten kein Platz ist
|
||||||
submenu.style.top = prefersBelow ? '0' : 'auto';
|
if (spaceBelow < rect.height && spaceAbove > rect.height) {
|
||||||
submenu.style.bottom = prefersBelow ? 'auto' : '100%';
|
submenu.style.top = 'auto';
|
||||||
|
submenu.style.bottom = `${parentRect.bottom - parentRect.top - rect.height}px`; // Höhe des Submenüs wird berücksichtigt
|
||||||
|
} else {
|
||||||
|
submenu.style.top = '0';
|
||||||
|
submenu.style.bottom = 'auto';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
<!-- Template for Subitems -->
|
{% macro render_icon_and_name(item) %}
|
||||||
{% macro render_subitems(subitems) %}
|
<i class="{{ item.icon.class if item.icon is defined and item.icon.class is defined else 'fa-solid fa-link' }}"></i>
|
||||||
{% for subitem in subitems %}
|
{% if item.name is defined %}
|
||||||
{% if subitem.subitems %}
|
{{ item.name }}
|
||||||
|
{% else %}
|
||||||
|
Unnamed Item: {{item}}
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
<!-- Template for children -->
|
||||||
|
{% macro render_children(children) %}
|
||||||
|
{% for children in children %}
|
||||||
|
{% if children.children %}
|
||||||
<li class="dropdown-submenu position-relative">
|
<li class="dropdown-submenu position-relative">
|
||||||
<a class="dropdown-item dropdown-toggle" href="#" title="{{ subitem.description }}">
|
<a class="dropdown-item dropdown-toggle" title="{{ children.description }}">
|
||||||
{% if subitem.icon is defined and subitem.icon.class is defined %}
|
{{ render_icon_and_name(children) }}
|
||||||
<i class="{{ subitem.icon.class }}"></i> {{ subitem.name }}
|
|
||||||
{% else %}
|
|
||||||
<p>Missing icon in subitem: {{ subitem }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{{ render_subitems(subitem.subitems) }}
|
{{ render_children(children.children) }}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% elif subitem.identifier or subitem.warning or subitem.info %}
|
{% elif children.identifier or children.warning or children.info %}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" onclick='openDynamicPopup({{ subitem|tojson|safe }})' data-bs-toggle="tooltip" title="{{ subitem.description }}">
|
<a class="dropdown-item" onclick='openDynamicPopup({{ children|tojson|safe }})' data-bs-toggle="tooltip" title="{{ children.description }}">
|
||||||
<i class="{{ subitem.icon.class }}"></i> {{ subitem.name }}
|
{{ render_icon_and_name(children) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{{ subitem.url }}" target="{{ subitem.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ subitem.description }}">
|
<a class="dropdown-item" href="{{ children.url }}" target="{{ children.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ children.description }}">
|
||||||
{% if subitem.icon is defined and subitem.icon.class is defined %}
|
{{ render_icon_and_name(children) }}
|
||||||
<i class="{{ subitem.icon.class }}"></i> {{ subitem.name }}
|
|
||||||
{% else %}
|
|
||||||
<p>Missing icon in subitem: {{ subitem }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -42,26 +42,26 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarNav{{menu_type}}">
|
<div class="collapse navbar-collapse" id="navbarNav{{menu_type}}">
|
||||||
<ul class="navbar-nav {% if menu_type == 'header' %}ms-auto{% endif %}">
|
<ul class="navbar-nav {% if menu_type == 'header' %}ms-auto{% endif %}">
|
||||||
{% for item in navigation[menu_type] %}
|
{% for item in navigation[menu_type].children %}
|
||||||
{% if item.url %}
|
{% if item.url %}
|
||||||
<!-- Single Item -->
|
<!-- Single Item -->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ item.url }}" target="{{ item.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ item.description }}">
|
<a class="nav-link" href="{{ item.url }}" target="{{ item.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ item.description }}">
|
||||||
<i class="{{ item.icon.class }}"></i> {{ item.name }}
|
{{ render_icon_and_name(item) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<!-- Dropdown Menu -->
|
<!-- Dropdown Menu -->
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown{{ loop.index }}" role="button" data-bs-toggle="dropdown" data-bs-display="dynamic" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" id="navbarDropdown{{ loop.index }}" role="button" data-bs-toggle="dropdown" data-bs-display="dynamic" aria-expanded="false">
|
||||||
{% if item.icon is defined and item.icon.class is defined %}
|
{% if item.icon is defined and item.icon.class is defined %}
|
||||||
<i class="{{ item.icon.class }}"></i> {{ item.name }}
|
{{ render_icon_and_name(item) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>Missing icon in item: {{ item }}</p>
|
<p>Missing icon in item: {{ item }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{{ render_subitems(item.subitems) }}
|
{{ render_children(item.children) }}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -2,7 +2,7 @@ from pprint import pprint
|
|||||||
class ConfigurationResolver:
|
class ConfigurationResolver:
|
||||||
"""
|
"""
|
||||||
A class to resolve `link` entries in a nested configuration structure.
|
A class to resolve `link` entries in a nested configuration structure.
|
||||||
Supports navigation through dictionaries, lists, and `subitems`.
|
Supports navigation through dictionaries, lists, and `children`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
@ -14,23 +14,56 @@ class ConfigurationResolver:
|
|||||||
"""
|
"""
|
||||||
self._recursive_resolve(self.config, self.config)
|
self._recursive_resolve(self.config, self.config)
|
||||||
|
|
||||||
|
def __load_children(self,path):
|
||||||
|
"""
|
||||||
|
Check if explicitly children should be loaded and not parent
|
||||||
|
"""
|
||||||
|
return path.split('.').pop() == "children"
|
||||||
|
|
||||||
|
def _replace_in_dict_by_dict(self, dict_origine, old_key, new_dict):
|
||||||
|
if old_key in dict_origine:
|
||||||
|
# Entferne den alten Key
|
||||||
|
old_value = dict_origine.pop(old_key)
|
||||||
|
# Füge die neuen Key-Value-Paare hinzu
|
||||||
|
dict_origine.update(new_dict)
|
||||||
|
|
||||||
|
def _replace_in_list_by_list(self, list_origine, old_element, new_elements):
|
||||||
|
index = list_origine.index(old_element)
|
||||||
|
list_origine[index:index+1] = new_elements
|
||||||
|
|
||||||
|
def _replace_element_in_list(self, list_origine, old_element, new_element):
|
||||||
|
index = list_origine.index(old_element)
|
||||||
|
list_origine[index] = new_element
|
||||||
|
|
||||||
def _recursive_resolve(self, current_config, root_config):
|
def _recursive_resolve(self, current_config, root_config):
|
||||||
"""
|
"""
|
||||||
Recursively resolves `link` entries in the configuration.
|
Recursively resolves `link` entries in the configuration.
|
||||||
"""
|
"""
|
||||||
if isinstance(current_config, dict):
|
if isinstance(current_config, dict):
|
||||||
for key, value in list(current_config.items()):
|
for key, value in list(current_config.items()):
|
||||||
if key == "link":
|
if key == "children":
|
||||||
|
if value is None or not isinstance(value, list):
|
||||||
|
raise ValueError(f"Expected 'children' to be a list, but got {type(value).__name__} instead.")
|
||||||
|
for item in value:
|
||||||
|
if "link" in item:
|
||||||
|
loaded_link = self._find_entry(root_config, item['link'].lower(), False)
|
||||||
|
if isinstance(loaded_link, list):
|
||||||
|
self._replace_in_list_by_list(value,item,loaded_link)
|
||||||
|
else:
|
||||||
|
self._replace_element_in_list(value,item,loaded_link)
|
||||||
|
else:
|
||||||
|
self._recursive_resolve(value, root_config)
|
||||||
|
elif key == "link":
|
||||||
try:
|
try:
|
||||||
target = self._find_entry(root_config, value.lower(), True)
|
loaded = self._find_entry(root_config, value.lower(), True)
|
||||||
if isinstance(target, list) and len(target) > 2:
|
if isinstance(loaded, list) and len(loaded) > 2:
|
||||||
target = self._find_entry(root_config, value.lower(), False)
|
loaded = self._find_entry(root_config, value.lower(), False)
|
||||||
current_config.clear()
|
current_config.clear()
|
||||||
current_config.update(target)
|
current_config.update(loaded)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Error resolving link '{value}': {str(e)}. "
|
f"Error resolving link '{value}': {str(e)}. "
|
||||||
f"Current path: {key}, Current config: {current_config}"
|
f"Current path: {key}, Current config: {current_config}" + (f", Loaded: {loaded}" if 'loaded' in locals() or 'loaded' in globals() else "")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._recursive_resolve(value, root_config)
|
self._recursive_resolve(value, root_config)
|
||||||
@ -38,9 +71,9 @@ class ConfigurationResolver:
|
|||||||
for item in current_config:
|
for item in current_config:
|
||||||
self._recursive_resolve(item, root_config)
|
self._recursive_resolve(item, root_config)
|
||||||
|
|
||||||
def _get_subitems(self,current):
|
def _get_children(self,current):
|
||||||
if isinstance(current, dict) and ("subitems" in current and current["subitems"]):
|
if isinstance(current, dict) and ("children" in current and current["children"]):
|
||||||
current = current["subitems"]
|
current = current["children"]
|
||||||
return current
|
return current
|
||||||
|
|
||||||
def _find_by_name(self,current, part):
|
def _find_by_name(self,current, part):
|
||||||
@ -49,33 +82,35 @@ class ConfigurationResolver:
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
def _find_entry(self, config, path, subitems):
|
def _find_entry(self, config, path, children):
|
||||||
"""
|
"""
|
||||||
Finds an entry in the configuration by a dot-separated path.
|
Finds an entry in the configuration by a dot-separated path.
|
||||||
Supports both dictionaries and lists with `subitems` navigation.
|
Supports both dictionaries and lists with `children` navigation.
|
||||||
"""
|
"""
|
||||||
parts = path.split('.')
|
parts = path.split('.')
|
||||||
current = config
|
current = config
|
||||||
for part in parts:
|
for part in parts:
|
||||||
if isinstance(current, list):
|
if isinstance(current, list):
|
||||||
# Look for a matching name in the list
|
# If children explicit declared just load children
|
||||||
found = self._find_by_name(current,part)
|
if part != "children":
|
||||||
if found:
|
# Look for a matching name in the list
|
||||||
print(
|
found = self._find_by_name(current,part)
|
||||||
f"Matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
|
if found:
|
||||||
f"Current list: {current}"
|
current = found
|
||||||
)
|
print(
|
||||||
else:
|
f"Matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
|
||||||
raise ValueError(
|
f"Current list: {current}"
|
||||||
f"No matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
|
)
|
||||||
f"Current list: {current}"
|
else:
|
||||||
)
|
raise ValueError(
|
||||||
current = found
|
f"No matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
|
||||||
|
f"Current list: {current}"
|
||||||
|
)
|
||||||
elif isinstance(current, dict):
|
elif isinstance(current, dict):
|
||||||
# Case-insensitive dictionary lookup
|
# Case-insensitive dictionary lookup
|
||||||
key = next((k for k in current if k.lower() == part), None)
|
key = next((k for k in current if k.lower() == part), None)
|
||||||
if key is None:
|
if key is None:
|
||||||
current = self._find_by_name(current["subitems"],part)
|
current = self._find_by_name(current["children"],part)
|
||||||
if not current:
|
if not current:
|
||||||
raise KeyError(
|
raise KeyError(
|
||||||
f"Key '{part}' not found in dictionary. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
|
f"Key '{part}' not found in dictionary. Path so far: {' > '.join(parts[:parts.index(part)+1])}. "
|
||||||
@ -89,8 +124,8 @@ class ConfigurationResolver:
|
|||||||
f"Invalid path segment '{part}'. Current type: {type(current)}. "
|
f"Invalid path segment '{part}'. Current type: {type(current)}. "
|
||||||
f"Path so far: {' > '.join(parts[:parts.index(part)+1])}"
|
f"Path so far: {' > '.join(parts[:parts.index(part)+1])}"
|
||||||
)
|
)
|
||||||
if subitems:
|
if children:
|
||||||
current = self._get_subitems(current)
|
current = self._get_children(current)
|
||||||
|
|
||||||
return current
|
return current
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user