Enhance navbar with sticky positioning and search functionality, including active link highlighting and improved mobile responsiveness
This commit is contained in:
+263
-8
@@ -63,6 +63,9 @@
|
|||||||
.navbar {
|
.navbar {
|
||||||
box-shadow: 0 4px 14px rgba(2, 6, 23, 0.24);
|
box-shadow: 0 4px 14px rgba(2, 6, 23, 0.24);
|
||||||
background-color: var(--module-primary-color) !important;
|
background-color: var(--module-primary-color) !important;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1900;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
@@ -82,6 +85,65 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link.nav-active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-search-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 10px;
|
||||||
|
width: min(420px, 42vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-search-form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-search-input {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 38px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 8px 12px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-search-input::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-search-input:focus {
|
||||||
|
border-color: #93c5fd;
|
||||||
|
box-shadow: 0 0 0 2px rgba(147, 197, 253, 0.35);
|
||||||
|
background: rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-search-btn {
|
||||||
|
min-height: 38px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.45);
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 7px 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-search-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-link-pill {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-nav .nav-link:hover,
|
.navbar-nav .nav-link:hover,
|
||||||
.navbar-nav .nav-link:focus {
|
.navbar-nav .nav-link:focus {
|
||||||
background-color: rgba(255, 255, 255, 0.12);
|
background-color: rgba(255, 255, 255, 0.12);
|
||||||
@@ -255,6 +317,27 @@
|
|||||||
.nav-item.dropdown .dropdown-toggle {
|
.nav-item.dropdown .dropdown-toggle {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-toggler {
|
||||||
|
padding: 0.5rem 0.65rem;
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-search-wrap {
|
||||||
|
width: 100%;
|
||||||
|
margin: 8px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-link {
|
||||||
|
min-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Better touch targets for mobile */
|
/* Better touch targets for mobile */
|
||||||
.dropdown-toggle::after {
|
.dropdown-toggle::after {
|
||||||
@@ -363,6 +446,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
{% set current_path = request.path %}
|
||||||
<!-- Module Selector Bar -->
|
<!-- Module Selector Bar -->
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
<div class="module-selector-bar" id="moduleBar">
|
<div class="module-selector-bar" id="moduleBar">
|
||||||
@@ -396,16 +480,29 @@
|
|||||||
<div class="collapse navbar-collapse" id="inventoryNavContent">
|
<div class="collapse navbar-collapse" id="inventoryNavContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="inventoryNavList">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="inventoryNavList">
|
||||||
<li class="nav-item" data-nav-fixed="true">
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
<a class="nav-link" href="{{ url_for('home') }}">Artikel</a>
|
<a class="nav-link {% if current_path == url_for('home') %}nav-active{% endif %}" href="{{ url_for('home') }}">Artikel</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if 'username' in session %}
|
||||||
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
|
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Ausleihen</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
|
<a class="nav-link quick-link-pill {% if current_path == url_for('notifications_view') %}nav-active{% endif %}" href="{{ url_for('notifications_view') }}">
|
||||||
|
Benachrichtigungen
|
||||||
|
{% if unread_notification_count and unread_notification_count > 0 %}
|
||||||
|
<span class="notif-badge">{{ unread_notification_count }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link nav-priority-link" href="{{ url_for('upload_admin') }}">➕ Hochladen</a>
|
<a class="nav-link nav-priority-link {% if current_path == url_for('upload_admin') %}nav-active{% endif %}" href="{{ url_for('upload_admin') }}">➕ Hochladen</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="nav-item dropdown ms-lg-auto">
|
<li class="nav-item dropdown ms-lg-auto">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="invMoreDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" href="#" id="invMoreDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" title="Weitere Optionen">
|
||||||
⋯ Mehr
|
Mehr Optionen
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invMoreDropdown">
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="invMoreDropdown">
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
@@ -435,6 +532,19 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
|
<div class="function-search-wrap">
|
||||||
|
<form class="function-search-form" data-function-search="true">
|
||||||
|
<input
|
||||||
|
class="function-search-input"
|
||||||
|
type="search"
|
||||||
|
name="function_search"
|
||||||
|
placeholder="Funktion suchen..."
|
||||||
|
list="function-search-options"
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<button class="function-search-btn" type="submit">Los</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<span class="navbar-text text-light me-3">{{ session['username'] }}</span>
|
<span class="navbar-text text-light me-3">{{ session['username'] }}</span>
|
||||||
<div class="dropdown me-2 user-menu-wrap">
|
<div class="dropdown me-2 user-menu-wrap">
|
||||||
<button class="btn btn-secondary dropdown-toggle user-menu-btn" type="button" id="invUserMenuDropdown" data-bs-toggle="dropdown" aria-expanded="false" data-notification-button="true">
|
<button class="btn btn-secondary dropdown-toggle user-menu-btn" type="button" id="invUserMenuDropdown" data-bs-toggle="dropdown" aria-expanded="false" data-notification-button="true">
|
||||||
@@ -466,16 +576,29 @@
|
|||||||
<div class="collapse navbar-collapse" id="libraryNavContent">
|
<div class="collapse navbar-collapse" id="libraryNavContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="libraryNavList">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="libraryNavList">
|
||||||
<li class="nav-item" data-nav-fixed="true">
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
<a class="nav-link" href="{{ url_for('library_view') }}">Medien</a>
|
<a class="nav-link {% if current_path == url_for('library_view') %}nav-active{% endif %}" href="{{ url_for('library_view') }}">Medien</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if 'username' in session %}
|
||||||
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
|
<a class="nav-link quick-link-pill {% if current_path == url_for('my_borrowed_items') %}nav-active{% endif %}" href="{{ url_for('my_borrowed_items') }}">Meine Medien</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" data-nav-fixed="true">
|
||||||
|
<a class="nav-link quick-link-pill {% if current_path == url_for('notifications_view') %}nav-active{% endif %}" href="{{ url_for('notifications_view') }}">
|
||||||
|
Benachrichtigungen
|
||||||
|
{% if unread_notification_count and unread_notification_count > 0 %}
|
||||||
|
<span class="notif-badge">{{ unread_notification_count }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link nav-priority-link" href="{{ url_for('library_admin') }}">📖 Hochladen</a>
|
<a class="nav-link nav-priority-link {% if current_path == url_for('library_admin') %}nav-active{% endif %}" href="{{ url_for('library_admin') }}">📖 Hochladen</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="nav-item dropdown ms-lg-auto">
|
<li class="nav-item dropdown ms-lg-auto">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="libMoreDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" href="#" id="libMoreDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" title="Weitere Optionen">
|
||||||
⋯ Mehr
|
Mehr Optionen
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="libMoreDropdown">
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="libMoreDropdown">
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
@@ -504,6 +627,19 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if 'username' in session %}
|
{% if 'username' in session %}
|
||||||
|
<div class="function-search-wrap">
|
||||||
|
<form class="function-search-form" data-function-search="true">
|
||||||
|
<input
|
||||||
|
class="function-search-input"
|
||||||
|
type="search"
|
||||||
|
name="function_search"
|
||||||
|
placeholder="Funktion suchen..."
|
||||||
|
list="function-search-options"
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<button class="function-search-btn" type="submit">Los</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<span class="navbar-text text-light me-3">{{ session['username'] }}</span>
|
<span class="navbar-text text-light me-3">{{ session['username'] }}</span>
|
||||||
<div class="dropdown me-2 user-menu-wrap">
|
<div class="dropdown me-2 user-menu-wrap">
|
||||||
<button class="btn btn-secondary dropdown-toggle user-menu-btn" type="button" id="libUserMenuDropdown" data-bs-toggle="dropdown" aria-expanded="false" data-notification-button="true">
|
<button class="btn btn-secondary dropdown-toggle user-menu-btn" type="button" id="libUserMenuDropdown" data-bs-toggle="dropdown" aria-expanded="false" data-notification-button="true">
|
||||||
@@ -704,6 +840,33 @@
|
|||||||
<span id="notification-toast-message"></span>
|
<span id="notification-toast-message"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<datalist id="function-search-options">
|
||||||
|
<option value="Artikel"></option>
|
||||||
|
<option value="Meine Ausleihen"></option>
|
||||||
|
<option value="Benachrichtigungen"></option>
|
||||||
|
<option value="Tutorial"></option>
|
||||||
|
<option value="Impressum"></option>
|
||||||
|
<option value="Lizenz"></option>
|
||||||
|
{% if library_module_enabled %}
|
||||||
|
<option value="Bibliothek"></option>
|
||||||
|
<option value="Meine Medien"></option>
|
||||||
|
{% endif %}
|
||||||
|
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
||||||
|
<option value="Hochladen"></option>
|
||||||
|
<option value="Ausleihen Verwaltung"></option>
|
||||||
|
<option value="Defekte Items"></option>
|
||||||
|
<option value="Filter verwalten"></option>
|
||||||
|
<option value="Orte verwalten"></option>
|
||||||
|
<option value="Audit Dashboard"></option>
|
||||||
|
<option value="Logs"></option>
|
||||||
|
<option value="Benutzer verwalten"></option>
|
||||||
|
<option value="Neuer Benutzer"></option>
|
||||||
|
{% if student_cards_module_enabled %}
|
||||||
|
<option value="Bibliotheksausweis"></option>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</datalist>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
function getCookie(name){
|
function getCookie(name){
|
||||||
@@ -746,6 +909,98 @@
|
|||||||
const notificationToastMessage = document.getElementById('notification-toast-message');
|
const notificationToastMessage = document.getElementById('notification-toast-message');
|
||||||
let lastUnreadCount = Number({{ unread_notification_count|default(0)|int }});
|
let lastUnreadCount = Number({{ unread_notification_count|default(0)|int }});
|
||||||
|
|
||||||
|
const functionSearchEntries = [
|
||||||
|
{ label: 'Artikel', keywords: ['artikel', 'inventar', 'home'], url: {{ url_for('home')|tojson }} },
|
||||||
|
{ label: 'Meine Ausleihen', keywords: ['meine ausleihen', 'ausleihen', 'borrowed'], url: {{ url_for('my_borrowed_items')|tojson }} },
|
||||||
|
{ label: 'Benachrichtigungen', keywords: ['benachrichtigungen', 'nachrichten', 'notifications'], url: {{ url_for('notifications_view')|tojson }} },
|
||||||
|
{ label: 'Tutorial', keywords: ['tutorial', 'hilfe', 'anleitung'], url: {{ url_for('tutorial_page')|tojson }} },
|
||||||
|
{ label: 'Impressum', keywords: ['impressum'], url: {{ url_for('impressum')|tojson }} },
|
||||||
|
{ label: 'Lizenz', keywords: ['lizenz', 'license'], url: {{ url_for('license')|tojson }} },
|
||||||
|
{% if library_module_enabled %}
|
||||||
|
{ label: 'Bibliothek', keywords: ['bibliothek', 'medien'], url: {{ url_for('library_view')|tojson }} },
|
||||||
|
{% endif %}
|
||||||
|
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
|
||||||
|
{ label: 'Hochladen', keywords: ['hochladen', 'upload'], url: {{ url_for('upload_admin')|tojson }} },
|
||||||
|
{ label: 'Ausleihen Verwaltung', keywords: ['ausleihen verwaltung', 'admin borrowings'], url: {{ url_for('admin_borrowings')|tojson }} },
|
||||||
|
{ label: 'Defekte Items', keywords: ['defekte items', 'defekt', 'schaden'], url: {{ url_for('admin_damaged_items')|tojson }} },
|
||||||
|
{ label: 'Filter verwalten', keywords: ['filter verwalten', 'filter'], url: {{ url_for('manage_filters')|tojson }} },
|
||||||
|
{ label: 'Orte verwalten', keywords: ['orte verwalten', 'orte', 'location'], url: {{ url_for('manage_locations')|tojson }} },
|
||||||
|
{ label: 'Audit Dashboard', keywords: ['audit', 'audit dashboard'], url: {{ url_for('admin_audit_dashboard')|tojson }} },
|
||||||
|
{ label: 'Logs', keywords: ['logs', 'protokoll'], url: {{ url_for('logs')|tojson }} },
|
||||||
|
{ label: 'Benutzer verwalten', keywords: ['benutzer verwalten', 'user'], url: {{ url_for('user_del')|tojson }} },
|
||||||
|
{ label: 'Neuer Benutzer', keywords: ['neuer benutzer', 'register'], url: {{ url_for('register')|tojson }} },
|
||||||
|
{% if library_module_enabled %}
|
||||||
|
{ label: 'Bibliotheks-Ausleihen', keywords: ['bibliotheks ausleihen', 'library loans'], url: {{ url_for('library_loans_admin')|tojson }} },
|
||||||
|
{% endif %}
|
||||||
|
{% if student_cards_module_enabled %}
|
||||||
|
{ label: 'Bibliotheksausweis', keywords: ['bibliotheksausweis', 'student card'], url: {{ url_for('student_cards_admin')|tojson }} },
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
];
|
||||||
|
|
||||||
|
function normalizeSearchText(value) {
|
||||||
|
return String(value || '')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[ä]/g, 'ae')
|
||||||
|
.replace(/[ö]/g, 'oe')
|
||||||
|
.replace(/[ü]/g, 'ue')
|
||||||
|
.replace(/[ß]/g, 'ss')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFunctionRoute(rawValue) {
|
||||||
|
const input = normalizeSearchText(rawValue);
|
||||||
|
if (!input) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let exact = null;
|
||||||
|
for (const entry of functionSearchEntries) {
|
||||||
|
const candidates = [entry.label].concat(entry.keywords || []);
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (normalizeSearchText(candidate) === input) {
|
||||||
|
exact = entry;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exact) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exact) {
|
||||||
|
return exact.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of functionSearchEntries) {
|
||||||
|
const candidates = [entry.label].concat(entry.keywords || []);
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (normalizeSearchText(candidate).includes(input) || input.includes(normalizeSearchText(candidate))) {
|
||||||
|
return entry.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindFunctionSearchForms() {
|
||||||
|
const forms = document.querySelectorAll('form[data-function-search="true"]');
|
||||||
|
forms.forEach(function(form) {
|
||||||
|
form.addEventListener('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const input = form.querySelector('input[name="function_search"]');
|
||||||
|
const targetUrl = findFunctionRoute(input ? input.value : '');
|
||||||
|
if (targetUrl) {
|
||||||
|
window.location.href = targetUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showInAppNotification('Keine Funktion gefunden', 'Bitte Suche verfeinern, z.B. "Defekte Items" oder "Benachrichtigungen".');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindFunctionSearchForms();
|
||||||
|
|
||||||
function updateNotificationDots(unreadCount) {
|
function updateNotificationDots(unreadCount) {
|
||||||
const hasUnread = Number(unreadCount) > 0;
|
const hasUnread = Number(unreadCount) > 0;
|
||||||
notificationButtons.forEach(function(btn) {
|
notificationButtons.forEach(function(btn) {
|
||||||
|
|||||||
Reference in New Issue
Block a user