Compare commits

...

5 Commits

8 changed files with 181 additions and 123 deletions

View File

@ -2,7 +2,7 @@
## Access
### Locale
http://127.0.0.1:5000
[http://127.0.0.1:5000](http://127.0.0.1:5000)
## Administrate Docker

View File

@ -17,16 +17,27 @@ accounts:
url: https://microblog.veen.world/@kevinveenbirkenbach
- name: Pictures
description: View my photo gallery
icon:
class: fa-solid fa-camera
url: https://picture.veen.world/kevinveenbirkenbach
class: fa-solid fa-images
subitems:
- name: Pixelfed
description: View my photo gallery
icon:
class: fa-solid fa-camera
url: https://s.veen.world/pictures
- name: Instagram
description: Follow me on Instagram
icon:
class: fa-brands fa-instagram
url: https://www.instagram.com/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.
- name: Videos
description: Watch my videos
icon:
class: fa-solid fa-video
url: https://video.veen.world/a/kevinveenbirkenbach
url: https://s.veen.world/videos
- name: Blog
description: Read my blog
@ -56,13 +67,6 @@ accounts:
class: fa-brands fa-meta
url:
subitems:
- name: Instagram
description: Follow me on Instagram
icon:
class: fa-brands fa-instagram
url: https://www.instagram.com/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.
- name: Facebook
description: Like my Facebook page
icon:
@ -502,6 +506,7 @@ navigation:
- 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

@ -1,3 +1,5 @@
@import url("navigation.css");
/* General link styles */
a {
text-decoration: none;
@ -77,67 +79,4 @@ h3.card-title {
h3.footer-title {
font-size: 1.3em;
}
/* Dropdown menu styles */
.dropdown-menu {
position: absolute !important;
}
.dropdown-menu-footer {
position: absolute !important;
top: auto !important;
bottom: 100%; /* Positions the menu above the trigger */
transform: translateY(-10px); /* Optional spacing for smoother appearance */
}
/* Dropdown submenu styles */
.dropdown-submenu {
position: relative;
list-style: none;
}
.dropdown-submenu > .dropdown-menu {
position: absolute;
top: 0;
left: 100%; /* Default position: open to the right */
margin-top: -1px;
z-index: 1050;
transition: opacity 0.3s ease-in-out; /* Smooth opacity transition */
}
/* Handle collapse behavior for dropdowns */
.dropdown-menu.collapse {
display: none;
}
.dropdown-menu.collapse.show {
display: block;
}
/* Ensure submenus are hidden by default */
.dropdown-submenu .dropdown-menu {
display: none;
opacity: 0;
transition: opacity 0.3s ease-in-out;
position: absolute;
left: 100%;
top: 0;
}
/* Show submenu on hover */
.dropdown-submenu:hover > .dropdown-menu {
display: block;
opacity: 1;
}
/* Ensure submenu remains visible when hovered over */
.dropdown-submenu:hover > .dropdown-menu:hover {
display: block;
opacity: 1;
}
/* Handle dynamic submenu positioning */
.dropdown-submenu > .dropdown-menu[style*="right: 100%"] {
left: auto; /* Override left position for leftward opening */
}
}

View File

@ -0,0 +1,36 @@
/* Dropdown-Menüs verstecken */
.dropdown-menu {
display: none;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
/* Dropdown-Menü beim Hover anzeigen */
.nav-item.dropdown:hover > .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;
top: 0;
left: 100%; /* Rechts ausklappen */
}
.dropdown-submenu.open > .dropdown-menu {
display: block;
opacity: 1;
visibility: visible;
}

119
app/static/js/navigation.js Normal file
View File

@ -0,0 +1,119 @@
document.addEventListener('DOMContentLoaded', () => {
const menuItems = document.querySelectorAll('.nav-item.dropdown');
const subMenuItems = document.querySelectorAll('.dropdown-submenu');
menuItems.forEach(item => {
let timeout;
// Öffnen beim Hovern
item.addEventListener('mouseenter', () => {
clearTimeout(timeout);
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 => {
let timeout;
// Öffnen beim Hovern
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);
}
});
});
// Globale Klick-Listener, um Menüs zu schließen, wenn außerhalb geklickt wird
document.addEventListener('click', () => {
[...menuItems, ...subMenuItems].forEach(item => closeMenu(item));
});
function openMenu(item, isTopLevel) {
item.classList.add('open');
const submenu = item.querySelector('.dropdown-menu');
if (submenu) {
adjustMenuPosition(submenu, item, isTopLevel);
submenu.style.display = 'block';
submenu.style.opacity = '1';
submenu.style.visibility = 'visible';
}
}
function closeMenu(item) {
item.classList.remove('open');
const submenu = item.querySelector('.dropdown-menu');
if (submenu) {
submenu.style.display = 'none';
submenu.style.opacity = '0';
submenu.style.visibility = 'hidden';
}
}
function adjustMenuPosition(submenu, parent, isTopLevel) {
const rect = submenu.getBoundingClientRect();
const parentRect = parent.getBoundingClientRect();
// Platzberechnung
const spaceAbove = parentRect.top;
const spaceBelow = window.innerHeight - parentRect.bottom;
const spaceLeft = parentRect.left;
const spaceRight = window.innerWidth - parentRect.right;
// Standardpositionierung
submenu.style.top = '';
submenu.style.bottom = '';
submenu.style.left = '';
submenu.style.right = '';
if (isTopLevel) {
// Top-Level-Menüs öffnen nur nach oben oder unten
if (spaceBelow < rect.height && spaceAbove > rect.height) {
submenu.style.bottom = '100%';
submenu.style.top = 'auto';
} else {
submenu.style.top = `${parentRect.height}px`;
submenu.style.bottom = 'auto';
}
} else {
// Submenüs öffnen in die Richtung mit mehr Platz
const prefersRight = spaceRight >= spaceLeft;
submenu.style.left = prefersRight ? '100%' : 'auto';
submenu.style.right = prefersRight ? 'auto' : '100%';
const prefersBelow = spaceBelow >= spaceAbove;
submenu.style.top = prefersBelow ? '0' : 'auto';
submenu.style.bottom = prefersBelow ? 'auto' : '100%';
}
}
});

View File

@ -1,41 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const dropdownSubmenus = document.querySelectorAll('.dropdown-submenu');
dropdownSubmenus.forEach(submenu => {
let timeout;
// Zeige das Submenü beim Hover
submenu.addEventListener('mouseenter', () => {
clearTimeout(timeout);
const menu = submenu.querySelector('.dropdown-menu');
if (menu) {
// Dynamische Positionierung
const rect = menu.getBoundingClientRect();
const viewportWidth = window.innerWidth;
// Überprüfen, ob Platz nach rechts ist, sonst nach links öffnen
if (rect.right > viewportWidth) {
menu.style.left = 'auto';
menu.style.right = '100%';
} else {
menu.style.left = '100%';
menu.style.right = 'auto';
}
menu.style.display = 'block';
menu.style.opacity = '1';
}
});
// Verstecke das Submenü nach 0.5 Sekunden
submenu.addEventListener('mouseleave', () => {
const menu = submenu.querySelector('.dropdown-menu');
if (menu) {
timeout = setTimeout(() => {
menu.style.display = 'none';
menu.style.opacity = '0';
}, 500); // 0.5 Sekunden Verzögerung
}
});
});
});

View File

@ -41,7 +41,7 @@
<!-- Include modal -->
{% include "moduls/modal.html.j2" %}
<script src="{{ url_for('static', filename='js/modal.js') }}"></script>
<script src="{{ url_for('static', filename='js/submenus.js') }}"></script>
<script src="{{ url_for('static', filename='js/navigation.js') }}"></script>
<script src="{{ url_for('static', filename='js/tooltip.js') }}"></script>
</body>
</html>

View File

@ -34,7 +34,7 @@
{% endfor %}
{% endmacro %}
<!-- Navigation Bar -->
<!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav{{menu_type}}" aria-controls="navbarNav{{menu_type}}" aria-expanded="false" aria-label="Toggle navigation">
@ -53,14 +53,14 @@
{% else %}
<!-- Dropdown Menu -->
<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" data-popper-placement="top" title="{{ item.description }}" aria-expanded="false">
<a class="nav-link dropdown-toggle" href="#" 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 %}
<i class="{{ item.icon.class }}" data-bs-toggle="tooltip"></i> {{ item.name }}
<i class="{{ item.icon.class }}"></i> {{ item.name }}
{% else %}
<p>Missing icon in item: {{ item }}</p>
{% endif %}
</a>
<ul class="dropdown-menu dropdown-menu-{{menu_type}}" aria-labelledby="navbarDropdown{{ loop.index }}">
<ul class="dropdown-menu">
{{ render_subitems(item.subitems) }}
</ul>
</li>
@ -69,4 +69,4 @@
</ul>
</div>
</div>
</nav>
</nav>