@@ -63,6 +63,9 @@
. navbar {
box-shadow : 0 4 px 14 px rgba ( 2 , 6 , 23 , 0.24 ) ;
background-color : var ( - - module - primary - color ) !important ;
position : sticky ;
top : 0 ;
z-index : 1900 ;
}
. navbar-brand {
@@ -82,6 +85,65 @@
border-radius : 8 px ;
}
. navbar-nav . nav-link . nav-active {
background-color : rgba ( 255 , 255 , 255 , 0.2 ) ;
box-shadow : inset 0 0 0 1 px rgba ( 255 , 255 , 255 , 0.35 ) ;
}
. function-search-wrap {
display : flex ;
align-items : center ;
margin-right : 10 px ;
width : min ( 420 px , 42 vw ) ;
}
. function-search-form {
width : 100 % ;
display : flex ;
gap : 6 px ;
align-items : center ;
}
. function-search-input {
width : 100 % ;
min-height : 38 px ;
border-radius : 10 px ;
border : 1 px solid rgba ( 255 , 255 , 255 , 0.3 ) ;
background : rgba ( 255 , 255 , 255 , 0.12 ) ;
color : #ffffff ;
padding : 8 px 12 px ;
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 2 px rgba ( 147 , 197 , 253 , 0.35 ) ;
background : rgba ( 255 , 255 , 255 , 0.18 ) ;
}
. function-search-btn {
min-height : 38 px ;
border-radius : 10 px ;
border : 1 px solid rgba ( 255 , 255 , 255 , 0.45 ) ;
background : rgba ( 255 , 255 , 255 , 0.15 ) ;
color : #ffffff ;
padding : 7 px 12 px ;
font-weight : 700 ;
}
. function-search-btn : hover {
background : rgba ( 255 , 255 , 255 , 0.26 ) ;
}
. quick-link-pill {
border : 1 px solid rgba ( 255 , 255 , 255 , 0.35 ) ;
background : rgba ( 255 , 255 , 255 , 0.08 ) ;
}
. navbar-nav . nav-link : hover ,
. navbar-nav . nav-link : focus {
background-color : rgba ( 255 , 255 , 255 , 0.12 ) ;
@@ -255,6 +317,27 @@
. nav-item . dropdown . dropdown-toggle {
padding : 8 px 12 px ;
}
. navbar-toggler {
padding : 0.5 rem 0.65 rem ;
border-width : 2 px ;
}
. function-search-wrap {
width : 100 % ;
margin : 8 px 0 10 px ;
}
. navbar-nav . nav-item {
margin-right : 0 ;
margin-bottom : 6 px ;
}
. navbar-nav . nav-link {
min-height : 44 px ;
display : flex ;
align-items : center ;
}
/* Better touch targets for mobile */
. dropdown-toggle :: after {
@@ -363,6 +446,7 @@
{% endblock %}
< / head >
< body >
{% set current_path = request.path %}
<!-- Module Selector Bar -->
{% if 'username' in session %}
< div class = "module-selector-bar" id = "moduleBar" >
@@ -396,28 +480,28 @@
< div class = "collapse navbar-collapse" id = "inventoryNavContent" >
< ul class = "navbar-nav me-auto mb-2 mb-lg-0" id = "inventoryNavList" >
< 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 >
{% 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('tutorial_page') %}nav-active{% endif %}" href = "{{ url_for('tutorial_page') }}" > Tutorial< / a >
< / li >
{% endif %}
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
< 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 >
{% endif %}
< 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" >
⋯ Mehr
< a class = "nav-link dropdown-toggle" href = "#" id = "invMoreDropdown" role = "button" data-bs-toggle = "dropdown" aria-expanded = "false" title = "Weitere Optionen" >
Mehr Optionen
< / a >
< ul class = "dropdown-menu dropdown-menu-end" aria-labelledby = "invMoreDropdown" >
{% if 'username' in session %}
< li > < a class = "dropdown-item" href = "{{ url_for('my_borrowed_items') }}" > Meine Ausleihen< / a > < / li >
< li >
< a class = "dropdown-item" 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 >
< li > < a class = "dropdown-item" href = "{{ url_for('tutorial_page') }}" > Tutorial< / a > < / li >
< li > < hr class = "dropdown-divider" > < / li >
{% endif %}
@@ -443,6 +527,19 @@
< / ul >
< div class = "d-flex" >
{% 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 >
< 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" >
@@ -474,28 +571,28 @@
< div class = "collapse navbar-collapse" id = "libraryNavContent" >
< ul class = "navbar-nav me-auto mb-2 mb-lg-0" id = "libraryNavList" >
< 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 >
{% 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('tutorial_page') %}nav-active{% endif %}" href = "{{ url_for('tutorial_page') }}" > Tutorial< / a >
< / li >
{% endif %}
{% if 'username' in session and (session.get('admin', False) or is_admin) %}
< 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 >
{% endif %}
< 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" >
⋯ Mehr
< a class = "nav-link dropdown-toggle" href = "#" id = "libMoreDropdown" role = "button" data-bs-toggle = "dropdown" aria-expanded = "false" title = "Weitere Optionen" >
Mehr Optionen
< / a >
< ul class = "dropdown-menu dropdown-menu-end" aria-labelledby = "libMoreDropdown" >
{% if 'username' in session %}
< li > < a class = "dropdown-item" href = "{{ url_for('my_borrowed_items') }}" > Meine Medien< / a > < / li >
< li >
< a class = "dropdown-item" 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 >
< li > < a class = "dropdown-item" href = "{{ url_for('tutorial_page') }}" > Tutorial< / a > < / li >
< li > < hr class = "dropdown-divider" > < / li >
{% endif %}
@@ -520,6 +617,19 @@
< / ul >
< div class = "d-flex" >
{% 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 >
< 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" >
@@ -653,9 +763,9 @@
width : 10 px ;
height : 10 px ;
border-radius : 50 % ;
background : #2563eb ;
background : #dc2626 ;
border : 2 px solid #ffffff ;
box-shadow : 0 0 0 1 px rgba ( 37 , 99 , 235 , 0.35 ) ;
box-shadow : 0 0 0 1 px rgba ( 220 , 38 , 38 , 0.35 ) ;
display : none ;
}
@@ -720,6 +830,33 @@
< span id = "notification-toast-message" > < / span >
< / 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 >
( function ( ) {
function getCookie ( name ) {
@@ -761,6 +898,126 @@
const notificationToastTitle = document . getElementById ( 'notification-toast-title' ) ;
const notificationToastMessage = document . getElementById ( 'notification-toast-message' ) ;
let lastUnreadCount = Number ( { { unread _notification _count | default ( 0 ) | int } } ) ;
const loginHintKey = username ? ( 'inventarsystem_notification_login_hint_v1_' + username ) : null ;
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 maybeShowLoginNotificationHint ( ) {
if ( ! username || ! loginHintKey ) {
return ;
}
if ( window . location . pathname === notificationsPagePath ) {
return ;
}
const alreadyShown = sessionStorage . getItem ( loginHintKey ) ;
if ( alreadyShown ) {
return ;
}
if ( lastUnreadCount > 0 ) {
const hintTitle = 'Neue Benachrichtigungen' ;
const hintMessage =
lastUnreadCount === 1
? 'Sie haben 1 neue Benachrichtigung. Im Nutzer-Menue finden Sie den Eintrag Benachrichtigungen.'
: 'Sie haben ' + lastUnreadCount + ' neue Benachrichtigungen. Im Nutzer-Menue finden Sie den Eintrag Benachrichtigungen.' ;
showInAppNotification ( hintTitle , hintMessage ) ;
}
sessionStorage . setItem ( loginHintKey , 'shown' ) ;
}
maybeShowLoginNotificationHint ( ) ;
function updateNotificationDots ( unreadCount ) {
const hasUnread = Number ( unreadCount ) > 0 ;