Compare commits

..

13 Commits

6 changed files with 496 additions and 431 deletions

View File

@ -1,136 +1,171 @@
--- ---
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:
@ -250,263 +285,280 @@ company:
country: Germany country: Germany
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
- name: Imprint description: View my Kanban board
description: Check out the imprint information icon:
icon: class: bi bi-clipboard2-check-fill
class: fa-solid fa-scale-balanced url: https://kanban.veen.world/
url: https://s.veen.world/imprint
- 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

View File

@ -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;
} }

View File

@ -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 */
.nav-item.dropdown:hover > .dropdown-menu, /* Positionierung von Submenüs */
.dropdown-submenu:hover > .dropdown-menu { .dropdown-submenu > .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;
}

View File

@ -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';
}
} }
} }
}); });

View File

@ -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 %}

View File

@ -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