Compare commits
7 Commits
643f9b3908
...
ef6145dfb6
| Author | SHA1 | Date | |
|---|---|---|---|
| ef6145dfb6 | |||
| d4605b952e | |||
| 007ad4581b | |||
| 8008d68c8b | |||
| 09296f077f | |||
| a76c2ec4a0 | |||
| e2ce075c96 |
55
analytics.py
55
analytics.py
@ -727,6 +727,8 @@ def file_access():
|
||||
if 'timeframe' not in session:
|
||||
session['timeframe'] = 'last24hours'
|
||||
session['timeframe'] = request.args.get('timeframe', session['timeframe'])
|
||||
if 'file_access_category' not in session:
|
||||
session['file_access_category'] = None
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
@ -775,25 +777,56 @@ def file_access():
|
||||
'category': hf.extract_structure_from_string(rel_path)[0]
|
||||
})
|
||||
|
||||
# Get possible categories from the rows
|
||||
categories = sorted({r['category'] for r in rows if r['category'] is not None})
|
||||
all_categories = [None] + categories
|
||||
top20 = []
|
||||
for category in all_categories:
|
||||
label = category if category is not None else 'Keine Kategorie gefunden !'
|
||||
files = [r for r in rows if r['category'] == category][:20]
|
||||
top20.append({
|
||||
'category': label,
|
||||
'files': files
|
||||
# Build a list of selectable categories for the dropdown
|
||||
category_options = []
|
||||
has_uncategorized = any(r['category'] is None for r in rows)
|
||||
if has_uncategorized:
|
||||
category_options.append({
|
||||
'value': 'uncategorized',
|
||||
'label': 'Keine Kategorie gefunden !'
|
||||
})
|
||||
|
||||
unique_categories = sorted({r['category'] for r in rows if r['category'] is not None})
|
||||
for category in unique_categories:
|
||||
category_options.append({
|
||||
'value': category,
|
||||
'label': category
|
||||
})
|
||||
|
||||
default_category_value = category_options[0]['value'] if category_options else None
|
||||
requested_category = request.args.get('category')
|
||||
stored_category = session.get('file_access_category')
|
||||
selected_category = requested_category or stored_category or default_category_value
|
||||
|
||||
valid_values = {opt['value'] for opt in category_options}
|
||||
if selected_category not in valid_values:
|
||||
selected_category = default_category_value
|
||||
|
||||
session['file_access_category'] = selected_category
|
||||
|
||||
def matches_category(row):
|
||||
if selected_category == 'uncategorized':
|
||||
return row['category'] is None
|
||||
return row['category'] == selected_category
|
||||
|
||||
filtered_rows = [r for r in rows if matches_category(r)] if selected_category is not None else []
|
||||
top20_files = filtered_rows[:20]
|
||||
|
||||
selected_category_label = next(
|
||||
(opt['label'] for opt in category_options if opt['value'] == selected_category),
|
||||
'Keine Daten verfügbar'
|
||||
)
|
||||
|
||||
title_short = app_config.get('TITLE_SHORT', 'Default Title')
|
||||
title_long = app_config.get('TITLE_LONG' , 'Default Title')
|
||||
|
||||
return render_template(
|
||||
"file_access.html",
|
||||
timeframe=session['timeframe'],
|
||||
top20 = top20,
|
||||
categories=category_options,
|
||||
selected_category=selected_category,
|
||||
selected_category_label=selected_category_label,
|
||||
top20_files=top20_files,
|
||||
admin_enabled=auth.is_admin(),
|
||||
title_short=title_short,
|
||||
title_long=title_long
|
||||
|
||||
4
app.py
4
app.py
@ -1224,7 +1224,9 @@ def create_token(subpath):
|
||||
token_qr_code=img_base64,
|
||||
token_folder=token_item.get('folders'),
|
||||
token_url=url,
|
||||
token_valid_to=token_item.get('validity', 'Unbekannt')
|
||||
token_valid_to=token_item.get('validity', 'Unbekannt'),
|
||||
title_short=auth.return_app_config().get('TITLE_SHORT', ''),
|
||||
title_long=auth.return_app_config().get('TITLE_LONG', '')
|
||||
)
|
||||
|
||||
|
||||
|
||||
539
static/app.css
539
static/app.css
@ -1,44 +1,57 @@
|
||||
/* Ensure html and body take full height */
|
||||
html, body {
|
||||
background-color: var(--light-background);
|
||||
background: var(--light-background);
|
||||
color: var(--main-text-color);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
/* padding: 0; */
|
||||
}
|
||||
|
||||
/* Global Styles */
|
||||
body {
|
||||
font-family: 'Helvetica Neue', Arial, sans-serif;
|
||||
padding-top: 70px; /* Adjust to your header height */
|
||||
overflow-x: hidden; /* Prevent horizontal scroll */
|
||||
font-family: 'Manrope', 'Segoe UI', sans-serif;
|
||||
padding-top: 0;
|
||||
overflow-x: hidden;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.6;
|
||||
background:
|
||||
radial-gradient(1200px 900px at -10% 10%, rgba(34, 211, 238, 0.12), transparent 40%),
|
||||
radial-gradient(1100px 800px at 110% -20%, rgba(59, 130, 246, 0.14), transparent 45%),
|
||||
var(--light-background);
|
||||
}
|
||||
|
||||
/* Header styles */
|
||||
.site-header {
|
||||
background-color: var(--dark-background);
|
||||
background: linear-gradient(120deg, var(--dark-background), #13253f);
|
||||
color: var(--header-text-color);
|
||||
position: fixed;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.site-header img.logo {
|
||||
height: 50px;
|
||||
margin-right: 15px;
|
||||
height: 54px;
|
||||
margin-right: 6px;
|
||||
filter: drop-shadow(0 8px 10px rgba(0, 0, 0, 0.25));
|
||||
}
|
||||
|
||||
.site-header h1 {
|
||||
font-size: 1.5em;
|
||||
font-size: 1.3rem;
|
||||
margin: 0;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
padding-bottom: 200px;
|
||||
padding-bottom: 220px;
|
||||
}
|
||||
|
||||
.wrapper > .admin-nav,
|
||||
@ -49,79 +62,179 @@ body {
|
||||
.wrapper > main,
|
||||
.wrapper > section {
|
||||
flex: 1 1 auto;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.wrapper > footer {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
padding: 20px 0;
|
||||
width: 95%;
|
||||
|
||||
}
|
||||
|
||||
.container a {
|
||||
font-size: 18px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
/* Carded content shell */
|
||||
main.tab-content,
|
||||
section.tab-content {
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: none;
|
||||
padding: 20px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
/* Search view container (custom element) */
|
||||
search {
|
||||
display: block;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0 0 16px 16px;
|
||||
box-shadow: none;
|
||||
margin-top: -1px;
|
||||
padding: 16px 12px 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Search view controls */
|
||||
.search-close-btn {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 14px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.input-clear-btn {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.input-clear-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.search-close-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Breadcrumb Styles */
|
||||
.breadcrumb a {
|
||||
font-size: 19px;
|
||||
text-decoration: none;
|
||||
color: var(--main-text-color);
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 16px;
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
font-size: 17px;
|
||||
text-decoration: none;
|
||||
color: var(--brand-navy);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.breadcrumb span {
|
||||
font-size: 19px;
|
||||
color: #ccc;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 17px;
|
||||
color: var(--muted-text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Admin Nav Styles */
|
||||
.admin-nav {
|
||||
color: #ccc;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
background-color: #979797;
|
||||
color: var(--brand-ink);
|
||||
border: 1px solid var(--border-color);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 12px;
|
||||
width: 100vw;
|
||||
margin-left: calc(50% - 50vw);
|
||||
margin-right: calc(50% - 50vw);
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.admin-nav-rail {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.admin-nav-track {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
padding: 0 10px; /* keep first/last items off the screen edge */
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.admin-nav a {
|
||||
color: #ccc;
|
||||
color: var(--brand-ink);
|
||||
text-decoration: none;
|
||||
padding: 5px 0;
|
||||
padding: 8px 12px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
border: 1px solid transparent;
|
||||
background: linear-gradient(135deg, #fff, #f4f7fb);
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.05);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.admin-nav a:hover {
|
||||
color: var(--brand-sky);
|
||||
border-color: var(--border-color);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.admin-nav span {
|
||||
color: var(--border-color);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admin-nav .dropdown-toggle {
|
||||
color: #ccc;
|
||||
text-decoration: none;
|
||||
padding: 5px 0;
|
||||
color: var(--brand-ink);
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.admin-nav .dropdown-toggle::after {
|
||||
border-top-color: #ccc;
|
||||
display: none; /* hide default Bootstrap caret */
|
||||
}
|
||||
|
||||
.admin-nav .dropdown-menu {
|
||||
background-color: #f2f2f2;
|
||||
border-color: #ccc;
|
||||
background-color: #fff;
|
||||
border-color: var(--border-color);
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--card-shadow);
|
||||
z-index: 4000;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%;
|
||||
margin-top: 0;
|
||||
margin-left: 8px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.admin-nav .dropdown-item {
|
||||
color: #333;
|
||||
color: var(--brand-ink);
|
||||
padding-left: 14px;
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
.admin-nav .dropdown-item:hover,
|
||||
.admin-nav .dropdown-item:focus {
|
||||
background-color: #e6e6e6;
|
||||
color: #000;
|
||||
background-color: #eef4ff;
|
||||
color: var(--brand-navy);
|
||||
}
|
||||
|
||||
/* List Styles */
|
||||
@ -138,18 +251,29 @@ div.directory-item a, li.directory-item a, li.file-item a, li.link-item a {
|
||||
|
||||
/* Directory Items (in a list) */
|
||||
.directory-item, .link-item, .directory-item, .file-item {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 12px;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
padding: 10px 15px;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--card-shadow);
|
||||
padding: 12px 18px;
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.directory-item:hover,
|
||||
.link-item:hover,
|
||||
.file-item:hover {
|
||||
border-color: var(--brand-sky);
|
||||
box-shadow: 0 14px 26px rgba(59, 130, 246, 0.14);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Grid Layout for Directories */
|
||||
.directories-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 0 10px;
|
||||
column-gap: 14px;
|
||||
row-gap: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -159,49 +283,58 @@ div.directory-item a, li.directory-item a, li.file-item a, li.link-item a {
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
/* Link Styles */
|
||||
.directory-link,
|
||||
.link-link,
|
||||
a.play-file {
|
||||
color: var(--player-text-color);
|
||||
color: var(--brand-ink);
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a.show-transcript {
|
||||
text-decoration: none;
|
||||
color: var(--main-text-color);
|
||||
font-size: 20px;
|
||||
color: var(--brand-navy);
|
||||
font-size: 18px;
|
||||
margin-left: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a.show-transcript:hover {
|
||||
color: rgb(113, 146, 167);
|
||||
color: var(--brand-sky);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: yellow;
|
||||
background-color: #fffbcc;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#transcriptContent code.timecode-link {
|
||||
cursor: pointer;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
|
||||
a.create-share {
|
||||
text-decoration: none;
|
||||
color: var(--main-text-color);
|
||||
font-size: 20px;
|
||||
color: var(--brand-navy);
|
||||
font-size: 18px;
|
||||
margin-left: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a.create-share:hover {
|
||||
color: rgb(113, 146, 167);
|
||||
color: var(--brand-sky);
|
||||
}
|
||||
|
||||
.currently-playing {
|
||||
background-color: var(--selected-background);
|
||||
border: 1px solid var(--brand-sky);
|
||||
}
|
||||
|
||||
/* Footer Player Styles */
|
||||
@ -210,14 +343,16 @@ footer {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--dark-background);
|
||||
color: #fff;
|
||||
padding: 7px;
|
||||
text-align: center;
|
||||
z-index: 1000; /* Make sure it sits on top */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none; /* avoid blocking underlying content */
|
||||
}
|
||||
|
||||
footer .audio-player-container {
|
||||
pointer-events: auto; /* keep controls clickable */
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
@ -235,21 +370,25 @@ footer {
|
||||
#transcriptModal .modal-content {
|
||||
background-color: #fff;
|
||||
margin: 5% auto;
|
||||
padding: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
position: relative;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#transcriptModal .close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 10px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* Basic Markdown Styles */
|
||||
@ -313,9 +452,10 @@ footer {
|
||||
.image-item {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 8px;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(145deg, #ffffff, #f6f8fc);
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
/* the filename overlay, centered at bottom */
|
||||
@ -422,41 +562,60 @@ footer {
|
||||
|
||||
/* Tab Navigation Styles */
|
||||
.main-tabs {
|
||||
background-color: var(--dark-background);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
gap: 2px;
|
||||
border-bottom: 2px solid #444;
|
||||
justify-content: flex-start;
|
||||
padding: 6px 18px 0;
|
||||
gap: 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
flex-wrap: nowrap;
|
||||
border-radius: 0;
|
||||
width: 100vw;
|
||||
margin-left: calc(50% - 50vw);
|
||||
margin-right: calc(50% - 50vw);
|
||||
box-sizing: border-box;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
background: #333;
|
||||
border: 1px solid #444;
|
||||
background: rgba(217, 217, 217, 0.5);
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom: none;
|
||||
color: #999;
|
||||
padding: 6px 20px;
|
||||
font-size: 14px;
|
||||
color: var(--muted-text);
|
||||
padding: 10px 16px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 5px 5px 0 0;
|
||||
margin: 0;
|
||||
border-radius: 10px 10px 0 0;
|
||||
margin: 0 6px 0 0;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: none;
|
||||
box-shadow:
|
||||
0 -10px 18px -12px rgba(15, 23, 42, 0.18),
|
||||
10px 0 18px -12px rgba(15, 23, 42, 0.12),
|
||||
-10px 0 18px -12px rgba(15, 23, 42, 0.12);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: #fff;
|
||||
background: #3a3a3a;
|
||||
color: var(--brand-ink);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: var(--main-text-color, #000);
|
||||
background: var(--light-background);
|
||||
border-color: #444;
|
||||
border-bottom-color: var(--light-background);
|
||||
z-index: 1;
|
||||
color: var(--brand-ink);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border-color: var(--border-color);
|
||||
z-index: 2;
|
||||
box-shadow:
|
||||
0 -12px 22px -12px rgba(15, 23, 42, 0.22),
|
||||
12px 0 20px -14px rgba(15, 23, 42, 0.14),
|
||||
-12px 0 20px -14px rgba(15, 23, 42, 0.14);
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
@ -471,27 +630,28 @@ footer {
|
||||
.messages-section-header {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 0 0 8px;
|
||||
padding: 0 0 10px;
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 0;
|
||||
padding: 10px 0 24px;
|
||||
}
|
||||
|
||||
.message-card {
|
||||
background: var(--card-background, #fff);
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(145deg, #ffffff, #f7f9ff);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
padding: 20px 22px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: box-shadow 0.3s ease;
|
||||
box-shadow: var(--card-shadow);
|
||||
transition: box-shadow 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.message-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
box-shadow: 0 20px 38px rgba(15, 23, 42, 0.16);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.message-header {
|
||||
@ -500,18 +660,18 @@ footer {
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
border-bottom: 1px dashed var(--border-color);
|
||||
}
|
||||
|
||||
.message-title {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
color: var(--main-text-color);
|
||||
color: var(--brand-ink);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.message-datetime {
|
||||
color: #666;
|
||||
color: var(--muted-text);
|
||||
font-size: 0.9em;
|
||||
font-style: italic;
|
||||
}
|
||||
@ -527,9 +687,9 @@ footer {
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
margin: 4px 0;
|
||||
background: #f0f4f8;
|
||||
color: #495057;
|
||||
border: 1px solid #d1dce5;
|
||||
background: #ecf6ff;
|
||||
color: #0f172a;
|
||||
border: 1px solid #bed8ff;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95em;
|
||||
font-weight: 500;
|
||||
@ -539,9 +699,9 @@ footer {
|
||||
}
|
||||
|
||||
.message-content .folder-link-btn:hover {
|
||||
background: #e3eaf0;
|
||||
border-color: #b8c5d0;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.12);
|
||||
background: #dff0ff;
|
||||
border-color: #93c5fd;
|
||||
box-shadow: 0 6px 18px rgba(14, 165, 233, 0.24);
|
||||
}
|
||||
|
||||
.message-content .folder-link-btn:active {
|
||||
@ -554,13 +714,13 @@ footer {
|
||||
}
|
||||
|
||||
.message-content a {
|
||||
color: #007bff;
|
||||
color: var(--brand-sky);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.message-content a:hover {
|
||||
color: #0056b3;
|
||||
color: #0ea5e9;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@ -620,10 +780,12 @@ footer {
|
||||
}
|
||||
|
||||
.message-content blockquote {
|
||||
border-left: 4px solid #ccc;
|
||||
border-left: 4px solid var(--border-color);
|
||||
padding-left: 15px;
|
||||
color: #666;
|
||||
color: var(--muted-text);
|
||||
font-style: italic;
|
||||
background: #f8fbff;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.message-image {
|
||||
@ -638,7 +800,7 @@ footer {
|
||||
.message-actions {
|
||||
margin-top: 15px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
@ -654,12 +816,12 @@ footer {
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #007bff;
|
||||
background: linear-gradient(135deg, #2563eb, #22d3ee);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background-color: #0056b3;
|
||||
background: linear-gradient(135deg, #1d4ed8, #0ea5e9);
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
@ -674,7 +836,7 @@ footer {
|
||||
.no-messages {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
color: var(--muted-text);
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
@ -690,9 +852,9 @@ footer {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid var(--border-color);
|
||||
border-top: 0;
|
||||
border-radius: 0 0 6px 6px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
@ -701,9 +863,9 @@ footer {
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
background: var(--card-background, #fff);
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 8px 10px 4px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 12px 12px 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
@ -713,19 +875,19 @@ footer {
|
||||
}
|
||||
|
||||
.calendar-day-today {
|
||||
background: #f7fbff;
|
||||
box-shadow: inset 0 0 0 2px #4da3ff;
|
||||
border-color: #4da3ff;
|
||||
background: linear-gradient(135deg, #f6fbff, #ecf4ff);
|
||||
box-shadow: inset 0 0 0 2px #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.calendar-day-sunday,
|
||||
.calendar-day-holiday {
|
||||
background: #fff7f7;
|
||||
background: #fff8f5;
|
||||
}
|
||||
|
||||
.calendar-day-today.calendar-day-sunday,
|
||||
.calendar-day-today.calendar-day-holiday {
|
||||
background: linear-gradient(135deg, #f7fbff 0%, #f7fbff 60%, #fff2f2 100%);
|
||||
background: linear-gradient(135deg, #f6fbff 0%, #f6fbff 60%, #fff1ed 100%);
|
||||
}
|
||||
|
||||
.calendar-day-sunday .calendar-day-header,
|
||||
@ -757,7 +919,7 @@ footer {
|
||||
@media (min-width: 992px) {
|
||||
.calendar-grid {
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
border-radius: 0 0 8px 8px;
|
||||
border-radius: 0 0 12px 12px;
|
||||
}
|
||||
|
||||
.calendar-weekday-header {
|
||||
@ -765,21 +927,21 @@ footer {
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #999;
|
||||
border: 1px solid #ddd;
|
||||
color: var(--muted-text);
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom: 0;
|
||||
border-radius: 8px 8px 0 0;
|
||||
border-radius: 12px 12px 0 0;
|
||||
overflow: visible;
|
||||
position: sticky;
|
||||
top: 70px;
|
||||
z-index: 900;
|
||||
background: var(--card-background, #fff);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.calendar-day:nth-child(7n) {
|
||||
@ -795,17 +957,17 @@ footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.calendar-day-name {
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.calendar-day-date {
|
||||
color: #666;
|
||||
color: var(--muted-text);
|
||||
font-size: 0.95rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@ -817,9 +979,9 @@ footer {
|
||||
}
|
||||
|
||||
.calendar-day thead th {
|
||||
background: #f1f1f1;
|
||||
color: #666;
|
||||
border-color: #ddd;
|
||||
background: #f6f8fb;
|
||||
color: var(--muted-text);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.calendar-day.empty thead {
|
||||
@ -851,7 +1013,7 @@ footer {
|
||||
|
||||
.details-indicator {
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
color: var(--brand-sky);
|
||||
margin-left: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
@ -869,7 +1031,7 @@ footer {
|
||||
}
|
||||
|
||||
.calendar-details-row td {
|
||||
background: #f8f9fa;
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
.calendar-inline-actions {
|
||||
@ -882,8 +1044,10 @@ footer {
|
||||
.file-browser-content {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
.file-browser-content .list-group-item {
|
||||
@ -906,15 +1070,98 @@ footer {
|
||||
}
|
||||
|
||||
.file-browser-panel {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
background-color: #f8fbff;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.file-browser-panel h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
color: var(--brand-ink);
|
||||
}
|
||||
/* Buttons - harmonize with dark blue palette */
|
||||
.btn {
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--brand-navy), var(--dark-background));
|
||||
border: 1px solid var(--brand-navy);
|
||||
color: #e5e7eb;
|
||||
box-shadow: 0 6px 14px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #1b2a44, #0d1524);
|
||||
border-color: #1b2a44;
|
||||
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:active,
|
||||
.btn-primary:focus {
|
||||
background: linear-gradient(135deg, #0d1524, #0a1020);
|
||||
border-color: #0d1524;
|
||||
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.22);
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #1f2e4a, #0f172a);
|
||||
border: 1px solid #1f2e4a;
|
||||
color: #e5e7eb;
|
||||
box-shadow: 0 6px 14px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
|
||||
.btn-secondary:hover,
|
||||
.btn-secondary:focus {
|
||||
background: linear-gradient(135deg, #223456, #111a2f);
|
||||
border-color: #223456;
|
||||
box-shadow: 0 8px 16px rgba(15, 23, 42, 0.2);
|
||||
}
|
||||
|
||||
.btn-secondary:active {
|
||||
background: linear-gradient(135deg, #0c1220, #0a0f1a);
|
||||
border-color: #0c1220;
|
||||
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.22);
|
||||
}
|
||||
|
||||
.btn-outline-secondary {
|
||||
border: 1px solid var(--brand-navy);
|
||||
color: var(--brand-navy);
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.btn-outline-secondary:hover,
|
||||
.btn-outline-secondary:focus {
|
||||
background: linear-gradient(135deg, #f7f9fd, #eef2f9);
|
||||
color: var(--brand-navy);
|
||||
border-color: var(--brand-navy);
|
||||
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.btn-outline-secondary:active {
|
||||
background: #e5eaf5;
|
||||
color: var(--brand-navy);
|
||||
border-color: var(--brand-navy);
|
||||
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.btn-light {
|
||||
background: linear-gradient(135deg, #e8ecf4, #dfe6f3);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--brand-ink);
|
||||
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.btn-light:hover,
|
||||
.btn-light:focus {
|
||||
background: linear-gradient(135deg, #dfe5f1, #d7dfef);
|
||||
color: var(--brand-ink);
|
||||
border-color: var(--brand-navy);
|
||||
}
|
||||
|
||||
@ -252,9 +252,9 @@ function renderContent(data) {
|
||||
contentHTML += '<div class="directories-grid">';
|
||||
data.directories.forEach(dir => {
|
||||
if (admin_enabled && data.breadcrumbs.length != 1 && dir.share) {
|
||||
share_link = `<a href="#" class="create-share" data-url="${dir.path}">⚙️</a>`;
|
||||
share_link = `<a href="#" class="create-share" data-url="${dir.path}"><i class="bi bi-gear"></i></a>`;
|
||||
}
|
||||
contentHTML += `<div class="directory-item"><a href="#" class="directory-link" data-path="${dir.path}">📁 ${dir.name}</a>
|
||||
contentHTML += `<div class="directory-item"><a href="#" class="directory-link" data-path="${dir.path}"><i class="bi bi-folder"></i> ${dir.name}</a>
|
||||
${share_link}
|
||||
</div>`;
|
||||
});
|
||||
@ -262,22 +262,22 @@ function renderContent(data) {
|
||||
} else {
|
||||
contentHTML += '<ul>';
|
||||
if (data.breadcrumbs.length === 1 && Array.isArray(data.folder_today) && data.folder_today.length > 0) {
|
||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="heute">📅 Heute</a></li>`;
|
||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="heute"><i class="bi bi-calendar2-day"></i> Heute</a></li>`;
|
||||
} else if (data.breadcrumbs.length === 1 && Array.isArray(data.folder_yesterday) && data.folder_yesterday.length > 0) {
|
||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="gestern">📅 Gestern</a></li>`;
|
||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="gestern"><i class="bi bi-calendar2-day"></i> Gestern</a></li>`;
|
||||
}
|
||||
if (data.breadcrumbs.length === 1 && data.toplist_enabled) {
|
||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="toplist">🔥 oft angehört</a></li>`;
|
||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="toplist"><i class="bi bi-graph-up"></i> oft angehört</a></li>`;
|
||||
}
|
||||
|
||||
data.directories.forEach(dir => {
|
||||
if (admin_enabled && data.breadcrumbs.length != 1 && dir.share) {
|
||||
share_link = `<a href="#" class="create-share" data-url="${dir.path}">⚙️</a>`;
|
||||
share_link = `<a href="#" class="create-share" data-url="${dir.path}"><i class="bi bi-gear"></i></a>`;
|
||||
}
|
||||
if (dir.path.includes('toplist')) {
|
||||
link_symbol = '⭐';
|
||||
link_symbol = '<i class="bi bi-star"></i>';
|
||||
} else {
|
||||
link_symbol = '📁';
|
||||
link_symbol = '<i class="bi bi-folder"></i>';
|
||||
}
|
||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="${dir.path}">${link_symbol} ${dir.name}</a>${share_link}</li>`;
|
||||
});
|
||||
@ -286,7 +286,7 @@ function renderContent(data) {
|
||||
}
|
||||
// Add search link at top level above directories/files
|
||||
if (data.breadcrumbs.length === 1) {
|
||||
contentHTML = `<ul><li class="link-item" onclick="viewSearch()"><a onclick="viewSearch()" class="link-link">🔎 Suche</a></li></ul>` + contentHTML;
|
||||
contentHTML = `<ul><li class="link-item" onclick="viewSearch()"><a onclick="viewSearch()" class="link-link"><i class="bi bi-search"></i> Suche</a></li></ul>` + contentHTML;
|
||||
}
|
||||
|
||||
// Render files (including music and non-image files)
|
||||
@ -294,18 +294,18 @@ function renderContent(data) {
|
||||
if (nonImageFiles.length > 0) {
|
||||
contentHTML += '<ul>';
|
||||
nonImageFiles.forEach((file, idx) => {
|
||||
let symbol = '📄';
|
||||
let symbol = '<i class="bi bi-file-earmark"></i>';
|
||||
if (file.file_type === 'music') {
|
||||
symbol = '🔊';
|
||||
symbol = '<i class="bi bi-volume-up"></i>';
|
||||
currentMusicFiles.push({ path: file.path, index: idx, title: file.name.replace('.mp3', '') });
|
||||
} else if (file.file_type === 'image') {
|
||||
symbol = '🖼️';
|
||||
symbol = '<i class="bi bi-image"></i>';
|
||||
}
|
||||
const indexAttr = file.file_type === 'music' ? ` data-index="${currentMusicFiles.length - 1}"` : '';
|
||||
contentHTML += `<li class="file-item">
|
||||
<a href="#" class="play-file"${indexAttr} data-url="${file.path}" data-file-type="${file.file_type}">${symbol} ${file.name.replace('.mp3', '')}</a>`;
|
||||
if (file.has_transcript) {
|
||||
contentHTML += `<a href="#" class="show-transcript" data-url="${file.transcript_url}" data-audio-url="${file.path}" title="Show Transcript">📄</a>`;
|
||||
contentHTML += `<a href="#" class="show-transcript" data-url="${file.transcript_url}" data-audio-url="${file.path}" title="Show Transcript"><i class="bi bi-journal-text"></i></a>`;
|
||||
}
|
||||
contentHTML += `</li>`;
|
||||
});
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
.audio-player-container {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 7px;
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #f9fbff, #eef4ff);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 12px 26px rgba(15, 23, 42, 0.12);
|
||||
border: 8px solid #0b1220;
|
||||
padding: 12px 12px;
|
||||
width: min(600px, 100%);
|
||||
display: none;
|
||||
color: #1f2937;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Now Playing Info */
|
||||
.now-playing-info {
|
||||
margin-top: 5px;
|
||||
font-size: 16px;
|
||||
color: var(--main-text-color);
|
||||
font-size: 15px;
|
||||
color: #1f2937;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.audio-player {
|
||||
--player-button-width: 4em;
|
||||
--sound-button-width: 3em;
|
||||
--control-size: 3.4em;
|
||||
--space: 0.5em;
|
||||
}
|
||||
|
||||
@ -26,6 +29,7 @@
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: var(--space); /* ensures spacing between controls */
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
/* Make the slider container fill the available space and stack its children vertically */
|
||||
@ -40,10 +44,10 @@
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
height: 0.5em;
|
||||
background-color: #ccc;
|
||||
background-color: #e6edf7;
|
||||
border-radius: 5px;
|
||||
background-size: 0% 100%;
|
||||
background-image: linear-gradient(var(--dark-background), var(--dark-background));
|
||||
background-image: linear-gradient(90deg, #0b1220, #0f172a);
|
||||
background-repeat: no-repeat;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
@ -56,9 +60,9 @@
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
background-color: var(--dark-background);
|
||||
background-color: #0f172a;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border: 2px solid #0b1220;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@ -67,9 +71,9 @@
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
background-color: var(--dark-background);
|
||||
background-color: #0f172a;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border: 2px solid #0b1220;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@ -78,9 +82,9 @@
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
background-color: var(--dark-background);
|
||||
background-color: #0f172a;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border: 2px solid #0b1220;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@ -104,18 +108,55 @@
|
||||
|
||||
.player-button,
|
||||
.sound-button {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
width: var(--control-size);
|
||||
height: var(--control-size);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.15s ease, transform 0.1s ease;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.player-button {
|
||||
width: var(--player-button-width);
|
||||
height: var(--player-button-width);
|
||||
.player-button:hover,
|
||||
.sound-button:hover {
|
||||
background: rgba(15, 23, 42, 0.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.sound-button {
|
||||
width: var(--sound-button-width);
|
||||
height: var(--sound-button-width);
|
||||
.player-button:active,
|
||||
.sound-button:active {
|
||||
transform: translateY(0);
|
||||
background: rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.player-button:focus-visible,
|
||||
.sound-button:focus-visible {
|
||||
outline: 2px solid rgba(20, 33, 61, 0.85);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.player-button svg,
|
||||
.sound-button svg {
|
||||
width: var(--control-size);
|
||||
height: var(--control-size);
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.player-button svg path,
|
||||
.sound-button svg path {
|
||||
fill: var(--dark-background);
|
||||
}
|
||||
|
||||
.timeline::-webkit-slider-thumb,
|
||||
.timeline::-moz-range-thumb,
|
||||
.timeline::-ms-thumb {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
@ -26,3 +26,44 @@ function toClipboard(url) {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
}
|
||||
|
||||
function printToken() {
|
||||
const printable = document.getElementById('tokenPrintable');
|
||||
if (!printable) {
|
||||
console.error('Token printable content not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const clone = printable.cloneNode(true);
|
||||
clone.querySelectorAll('.token-action-buttons').forEach(el => el.remove());
|
||||
|
||||
const styles = Array.from(document.querySelectorAll('link[rel="stylesheet"], style'))
|
||||
.map(node => node.outerHTML)
|
||||
.join('\n');
|
||||
|
||||
const popup = window.open('', '_blank', 'width=900,height=1200');
|
||||
if (!popup) {
|
||||
alert('Bitte Popups erlauben, um das PDF zu speichern.');
|
||||
return;
|
||||
}
|
||||
|
||||
popup.document.write(`<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Token PDF</title>
|
||||
${styles}
|
||||
<style>
|
||||
body { padding: 24px; }
|
||||
.token-action-buttons { display: none !important; }
|
||||
.card { box-shadow: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>${clone.outerHTML}</body>
|
||||
</html>`);
|
||||
popup.document.close();
|
||||
popup.focus();
|
||||
popup.onload = () => {
|
||||
popup.print();
|
||||
popup.close();
|
||||
};
|
||||
}
|
||||
|
||||
@ -417,7 +417,7 @@ function setAutoPlay(active) {
|
||||
const playButton = document.getElementById('gallery-play');
|
||||
isAutoPlay = active;
|
||||
if (playButton) {
|
||||
playButton.textContent = active ? '⏸' : '▶';
|
||||
playButton.textContent = active ? '||' : '>';
|
||||
playButton.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
playButton.classList.toggle('playing', active);
|
||||
}
|
||||
|
||||
@ -1,6 +1,66 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// (No per-icon setup needed here—just ensure your CSS is in place.)
|
||||
function wireAdminDropdowns() {
|
||||
const reposition = (toggle, menu) => {
|
||||
const rect = toggle.getBoundingClientRect();
|
||||
const gutter = 8;
|
||||
const menuWidth = menu.offsetWidth || 220;
|
||||
const menuHeight = menu.offsetHeight || 0;
|
||||
|
||||
// Prefer placing the menu to the right of the toggle; clamp within viewport.
|
||||
let left = rect.right + gutter;
|
||||
if (left + menuWidth > window.innerWidth - gutter) {
|
||||
left = Math.max(gutter, window.innerWidth - menuWidth - gutter);
|
||||
}
|
||||
|
||||
let top = Math.max(gutter, Math.min(rect.top, window.innerHeight - menuHeight - gutter));
|
||||
|
||||
menu.style.position = 'fixed';
|
||||
menu.style.transform = 'none';
|
||||
menu.style.top = `${top}px`;
|
||||
menu.style.left = `${left}px`;
|
||||
menu.style.right = 'auto';
|
||||
menu.style.minWidth = `${Math.max(rect.width, 160)}px`;
|
||||
menu.style.maxWidth = '260px';
|
||||
menu.style.display = 'block';
|
||||
menu.dataset.dropdownOpen = '1';
|
||||
menu.classList.add('show');
|
||||
};
|
||||
|
||||
const resetMenu = (menu) => {
|
||||
menu.style.position = '';
|
||||
menu.style.transform = '';
|
||||
menu.style.top = '';
|
||||
menu.style.left = '';
|
||||
menu.style.right = '';
|
||||
menu.style.minWidth = '';
|
||||
menu.style.maxWidth = '';
|
||||
menu.style.display = '';
|
||||
menu.classList.remove('show');
|
||||
delete menu.dataset.dropdownOpen;
|
||||
};
|
||||
|
||||
document.querySelectorAll('.admin-nav .dropdown-toggle').forEach((toggle) => {
|
||||
if (toggle.dataset.dropdownWired) return;
|
||||
toggle.dataset.dropdownWired = '1';
|
||||
const menu = toggle.nextElementSibling;
|
||||
if (!menu) return;
|
||||
|
||||
toggle.addEventListener('shown.bs.dropdown', () => reposition(toggle, menu));
|
||||
toggle.addEventListener('hide.bs.dropdown', () => resetMenu(menu));
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (menu.dataset.dropdownOpen) reposition(toggle, menu);
|
||||
}, { passive: true });
|
||||
window.addEventListener('resize', () => {
|
||||
if (menu.dataset.dropdownOpen) reposition(toggle, menu);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
wireAdminDropdowns();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', wireAdminDropdowns);
|
||||
}
|
||||
|
||||
// 1) Delegate clicks on any .toggle-icon, now or in the future:
|
||||
document.addEventListener('click', function(e) {
|
||||
|
||||
@ -14,11 +14,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
card.className = 'card';
|
||||
card.innerHTML = `
|
||||
<div class="card-body">
|
||||
<p><button class="btn btn-light" onclick="player.loadTrack('${file.relative_path}')" style="width:100%;">🔊 ${filenameWithoutExtension}</button></p>
|
||||
<p><button onclick="window.open('/path/${file.relative_path}', '_self');" class="btn btn-light btn-sm" style="width:100%;">📁 ${parentFolder}</button></p>
|
||||
<p><button class="btn btn-light" onclick="player.loadTrack('${file.relative_path}')" style="width:100%;"><i class="bi bi-volume-up"></i> ${filenameWithoutExtension}</button></p>
|
||||
<p><button onclick="window.open('/path/${file.relative_path}', '_self');" class="btn btn-light btn-sm" style="width:100%;"><i class="bi bi-folder"></i> ${parentFolder}</button></p>
|
||||
<p class="card-text">Anzahl Downloads: ${file.hitcount}</p>
|
||||
${ file.performance_date !== undefined ? `<p class="card-text">Datum: ${file.performance_date}</p>` : ``}
|
||||
${ file.transcript_hits !== undefined ? `<p class="card-text">Treffer im Transkript: ${file.transcript_hits} <a href="#" class="show-transcript" data-url="${transcriptURL}" data-audio-url="${file.relative_path}" highlight="${file.query}">📄</a></p>` : ``}
|
||||
${ file.transcript_hits !== undefined ? `<p class="card-text">Treffer im Transkript: ${file.transcript_hits} <a href="#" class="show-transcript" data-url="${transcriptURL}" data-audio-url="${file.relative_path}" highlight="${file.query}"><i class="bi bi-journal-text"></i></a></p>` : ``}
|
||||
</div>
|
||||
`;
|
||||
resultsDiv.appendChild(card);
|
||||
@ -66,6 +66,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
e.preventDefault();
|
||||
const query = document.getElementById('query').value.trim();
|
||||
const includeTranscript = document.getElementById('includeTranscript').checked;
|
||||
const spinnerTimer = setTimeout(() => {
|
||||
if (typeof showSpinner === 'function') showSpinner();
|
||||
}, 200);
|
||||
|
||||
// Get the selected category radio button, if any
|
||||
const categoryRadio = document.querySelector('input[name="category"]:checked');
|
||||
@ -96,7 +99,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
formData.append('dateto', document.getElementById('dateto').value);
|
||||
formData.append('includeTranscript', includeTranscript);
|
||||
|
||||
fetch('/searchcommand', {
|
||||
const settleSpinner = () => {
|
||||
clearTimeout(spinnerTimer);
|
||||
if (typeof hideSpinner === 'function') hideSpinner();
|
||||
};
|
||||
|
||||
const fetchPromise = fetch('/searchcommand', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
@ -118,6 +126,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
// Always clear/hide spinner once the request settles
|
||||
if (typeof fetchPromise.finally === 'function') {
|
||||
fetchPromise.finally(settleSpinner);
|
||||
} else {
|
||||
fetchPromise.then(settleSpinner, settleSpinner);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear button event handler
|
||||
|
||||
80
static/token.css
Normal file
80
static/token.css
Normal file
@ -0,0 +1,80 @@
|
||||
.token-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#tokenPrintable .qr-code {
|
||||
max-width: 420px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#tokenPrintable .token-qr-wrap {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#tokenPrintable .token-pdf-logo {
|
||||
max-width: 120px;
|
||||
width: 28%;
|
||||
min-width: 80px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.8)) drop-shadow(0 0 14px rgba(0, 0, 0, 0.6));
|
||||
}
|
||||
|
||||
#tokenPrintable .token-folder-title {
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
#tokenPrintable .token-page-title {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#tokenPrintable .copy-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
border: 1px solid #007bff;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
#tokenPrintable .copy-btn:active {
|
||||
background-color: #0056b3;
|
||||
border-color: #0056b3;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.token-action-buttons {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#tokenPrintable .qr-code {
|
||||
max-width: 620px;
|
||||
width: 90% !important;
|
||||
}
|
||||
|
||||
#tokenPrintable .token-pdf-logo {
|
||||
max-width: 140px;
|
||||
width: 24%;
|
||||
}
|
||||
|
||||
#tokenPrintable .token-qr-wrap {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@ -1,77 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
{% extends 'base.html' %}
|
||||
|
||||
<title>{{ title_short }}</title>
|
||||
{% block title %}{{ title_short }}{% endblock %}
|
||||
|
||||
<meta property="og:title" content="{{ og_title }}" />
|
||||
<meta property="og:description" content="{{ og_description }}" />
|
||||
<meta property="og:image" content="/icon/logo-192x192.png" />
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
{% block head_extra %}
|
||||
<meta property="og:title" content="{{ og_title }}">
|
||||
<meta property="og:description" content="{{ og_description }}">
|
||||
<meta property="og:image" content="/icon/logo-192x192.png">
|
||||
<meta property="og:type" content="website">
|
||||
<meta name="description" content="... uns aber, die wir gerettet werden, ist es eine Gotteskraft.">
|
||||
<meta name="author" content="{{ title_short }}">
|
||||
<link rel="icon" href="/icon/logo-192x192.png" type="image/png" sizes="192x192">
|
||||
|
||||
|
||||
<!-- Web App Manifest -->
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||
|
||||
<!-- Android Theme Color -->
|
||||
<meta name="theme-color" content="#000">
|
||||
|
||||
<!-- Apple-specific tags -->
|
||||
<link rel="touch-icon" href="/icon/logo-192x192.png">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="mobile-web-app-title" content="Gottesdienste">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
<!-- Your CSS -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='theme.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='gallery.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='audioplayer.css') }}">
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script>const admin_enabled = {{ admin_enabled | tojson | safe }};</script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="{{ url_for('static', filename='functions.js') }}"></script>
|
||||
<header class="site-header">
|
||||
<a href="/">
|
||||
<img src="/custom_logo/logoW.png" alt="Logo" class="logo">
|
||||
</a>
|
||||
<h1>{{ title_long }}</h1>
|
||||
</header>
|
||||
<div class="wrapper">
|
||||
{% if admin_enabled %}
|
||||
<div class="admin-nav">
|
||||
<a href="{{ url_for('mylinks') }}">Meine Links</a>
|
||||
<span> | </span>
|
||||
<a href="{{ url_for('folder_secret_config_editor') }}" id="edit-folder-config">Ordnerkonfiguration</a>
|
||||
<span> | </span>
|
||||
<div class="dropdown d-inline-block">
|
||||
<a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Auswertungen
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{{ url_for('dashboard') }}">Dashbord</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('connections') }}">Verbindungen</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('file_access') }}">Dateizugriffe</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('songs_dashboard') }}">Wiederholungen</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('search_db_analyzer') }}">Dateiindex Analyse</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% if features %}
|
||||
{% block content %}
|
||||
<!-- Tab Navigation -->
|
||||
{% if features %}
|
||||
<nav class="main-tabs">
|
||||
{% if "files" in features %}<button class="tab-button active" data-tab="browse">Audio/Photo</button>{% endif %}
|
||||
{% if "messages" in features %}<button class="tab-button" data-tab="messages">Nachrichten</button>{% endif %}
|
||||
@ -81,7 +33,6 @@
|
||||
|
||||
<!-- Browse Section -->
|
||||
<main id="browse-section" class="tab-content active">
|
||||
<div class="container">
|
||||
<div id="breadcrumbs" class="breadcrumb"></div>
|
||||
<div id="content"></div>
|
||||
<div id="directory-controls">
|
||||
@ -93,20 +44,24 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{% include 'messages_section.html' %}
|
||||
{% include 'calendar_section.html' %}
|
||||
|
||||
<search style="display: none;">
|
||||
<button type="button" id="backBtn" class="btn-close search-close-btn" aria-label="beenden"></button>
|
||||
<div class="container">
|
||||
<form id="searchForm" method="post" class="mb-4">
|
||||
|
||||
<!-- Suchwörter -->
|
||||
<div class="mb-3">
|
||||
<div class="mb-3 search-query-wrap">
|
||||
<label for="query" class="h5 form-label">Suchwörter:</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="query" name="query" class="form-control" required>
|
||||
<button type="button" id="clearBtn" class="btn btn-outline-secondary input-clear-btn" aria-label="zurücksetzen">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toggle für Suchoptionen -->
|
||||
@ -125,7 +80,6 @@
|
||||
|
||||
<!-- Suchoptionen einklappbar -->
|
||||
<div id="searchOptions" class="collapse border rounded p-3 mb-3">
|
||||
|
||||
<!-- Kategorie -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Kategorie:</label>
|
||||
@ -191,17 +145,12 @@
|
||||
<input type="date" id="dateto" name="dateto" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Ende Suchoptionen -->
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary">Suchen</button>
|
||||
<button type="button" id="clearBtn" class="btn btn-secondary ms-2">zurücksetzen</button>
|
||||
<button type="button" id="backBtn" class="btn btn-secondary ms-2">beenden</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<!-- AJAX-loaded results -->
|
||||
@ -209,8 +158,6 @@
|
||||
</div>
|
||||
</search>
|
||||
|
||||
|
||||
|
||||
<!-- Global Audio Player in Footer -->
|
||||
<footer>
|
||||
<div class="audio-player-container" id="audioPlayerContainer">
|
||||
@ -220,17 +167,14 @@
|
||||
</audio>
|
||||
<div class="controls">
|
||||
<button class="player-button icon-color">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="slider">
|
||||
<input type="range" class="timeline" max="100" value="0" step="0.1">
|
||||
<div id="timeInfo" class="now-playing-info"></div>
|
||||
</div>
|
||||
<button class="sound-button icon-color" onclick="player.fileDownload()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 120.89" width="35" height="35">
|
||||
<path fill-rule="evenodd" d="M84.58,47a7.71,7.71,0,1,1,10.8,11L66.09,86.88a7.72,7.72,0,0,1-10.82,0L26.4,58.37a7.71,7.71,0,1,1,10.81-11L53.1,63.12l.16-55.47a7.72,7.72,0,0,1,15.43.13l-.15,55L84.58,47ZM0,113.48.1,83.3a7.72,7.72,0,1,1,15.43.14l-.07,22q46,.09,91.91,0l.07-22.12a7.72,7.72,0,1,1,15.44.14l-.1,30h-.09a7.71,7.71,0,0,1-7.64,7.36q-53.73.1-107.38,0A7.7,7.7,0,0,1,0,113.48Z"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M12 2.25A9.75 9.75 0 1 1 2.25 12 9.75 9.75 0 0 1 12 2.25Zm0 5a.75.75 0 0 0-.75.75v4.19l-1.72-1.72a.75.75 0 1 0-1.06 1.06l3.03 3.03a.75.75 0 0 0 1.06 0l3.03-3.03a.75.75 0 1 0-1.06-1.06L12.75 12.2V8a.75.75 0 0 0-.75-.75ZM7.5 15.25a.75.75 0 0 0 0 1.5h9a.75.75 0 0 0 0-1.5Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@ -240,7 +184,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Transcript Modal -->
|
||||
<div id="transcriptModal">
|
||||
@ -254,10 +197,10 @@
|
||||
<div id="gallery-modal" style="display: none;">
|
||||
<button id="gallery-close">x</button>
|
||||
<button id="gallery-info" aria-expanded="false" aria-controls="gallery-exif">i</button>
|
||||
<img id="gallery-modal-content" src="" />
|
||||
<img id="gallery-modal-content" src="">
|
||||
<button class="gallery-nav gallery-prev">‹</button>
|
||||
<button class="gallery-nav gallery-next">›</button>
|
||||
<button id="gallery-play" aria-pressed="false">▶</button>
|
||||
<button id="gallery-play" aria-pressed="false" aria-label="Start slideshow">></button>
|
||||
<div id="gallery-exif" aria-live="polite"></div>
|
||||
<div id="gallery-filename" aria-live="polite"></div>
|
||||
</div>
|
||||
@ -269,10 +212,9 @@
|
||||
<div id="fullscreen-loader" style="display: none;">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Load main app JS first, then gallery JS -->
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='audioplayer.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='app.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='gallery.js') }}"></script>
|
||||
@ -281,9 +223,8 @@
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('{{ url_for("static", filename="sw.js") }}')
|
||||
navigator.serviceWorker.register('{{ url_for("static", filename="sw.js") }}');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
@ -10,6 +10,10 @@
|
||||
<!-- Android Theme Color -->
|
||||
<meta name="theme-color" content="#000">
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='theme.css') }}">
|
||||
@ -29,14 +33,16 @@
|
||||
{% if admin_enabled %}
|
||||
<!-- Navigation Bar -->
|
||||
<div class="admin-nav">
|
||||
<div class="admin-nav-rail">
|
||||
<div class="admin-nav-track">
|
||||
<a href="{{ url_for('index') }}">App</a>
|
||||
<span> | </span>
|
||||
<a href="{{ url_for('mylinks') }}">Meine Links</a>
|
||||
<span> | </span>
|
||||
<a href="{{ url_for('folder_secret_config_editor') }}" id="edit-folder-config">Ordnerkonfiguration</a>
|
||||
<span> | </span>
|
||||
<div class="dropdown d-inline-block">
|
||||
<a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<div class="dropdown dropend d-inline-block">
|
||||
<a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" data-bs-display="static" aria-expanded="false">
|
||||
Auswertungen
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
@ -48,13 +54,15 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
<script src="{{ url_for('static', filename='general.js') }}"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='general.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -34,47 +34,63 @@
|
||||
<ul class="dropdown-menu" aria-labelledby="timeframeDropdown">
|
||||
<li>
|
||||
<a class="dropdown-item {% if session['timeframe'] == 'last24hours' %}active{% endif %}"
|
||||
href="{{ url_for('file_access', timeframe='last24hours') }}">
|
||||
href="{{ url_for('file_access', timeframe='last24hours', category=selected_category) }}">
|
||||
Last 24 Hours
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item {% if session['timeframe'] == '7days' %}active{% endif %}"
|
||||
href="{{ url_for('file_access', timeframe='7days') }}">
|
||||
href="{{ url_for('file_access', timeframe='7days', category=selected_category) }}">
|
||||
Last 7 Days
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item {% if session['timeframe'] == '14days' %}active{% endif %}"
|
||||
href="{{ url_for('file_access', timeframe='14days') }}">
|
||||
href="{{ url_for('file_access', timeframe='14days', category=selected_category) }}">
|
||||
Last 14 Days
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item {% if session['timeframe'] == '30days' %}active{% endif %}"
|
||||
href="{{ url_for('file_access', timeframe='30days') }}">
|
||||
href="{{ url_for('file_access', timeframe='30days', category=selected_category) }}">
|
||||
Last 30 Days
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item {% if session['timeframe'] == '365days' %}active{% endif %}"
|
||||
href="{{ url_for('file_access', timeframe='365days') }}">
|
||||
href="{{ url_for('file_access', timeframe='365days', category=selected_category) }}">
|
||||
Last 365 Days
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Category Dropdown -->
|
||||
{% if categories %}
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary dropdown-toggle"
|
||||
type="button" id="categoryDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{ selected_category_label }}
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="categoryDropdown">
|
||||
{% for option in categories %}
|
||||
<li>
|
||||
<a class="dropdown-item {% if selected_category == option.value %}active{% endif %}"
|
||||
href="{{ url_for('file_access', timeframe=session['timeframe'], category=option.value) }}">
|
||||
{{ option.label }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Detailed Table of Top File Accesses -->
|
||||
{% for top20_item in top20 %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span>{{ top20_item['category'] }}</span>
|
||||
<span class="toggle-icon" aria-label="collapse" role="button" tabindex="0">+</span>
|
||||
</div>
|
||||
<div class="card-body collapsable">
|
||||
<div class="card-header">{{ selected_category_label }}</div>
|
||||
<div class="card-body">
|
||||
{% if top20_files %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
@ -84,23 +100,20 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in top20_item['files'] %}
|
||||
{% for row in top20_files %}
|
||||
<tr>
|
||||
<td>{{ row.access_count }}</td>
|
||||
<td>{{ row.rel_path }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="2">No data available for the selected timeframe.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="mb-0">No data available for the selected timeframe and category.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@ -1,51 +1,34 @@
|
||||
<style>
|
||||
.container-fluid {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.qr-code {
|
||||
max-width: 300px;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
.copy-btn {
|
||||
margin-top: 1rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
border: 1px solid #007bff;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.copy-btn:active {
|
||||
background-color: #0056b3;
|
||||
border-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='token.css') }}">
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="card h-100 shadow-sm">
|
||||
<div class="container-fluid token-container">
|
||||
{% set folder_names = token_folder | map(attribute='foldername') | list %}
|
||||
<div class="card h-100 shadow-sm text-center" id="tokenPrintable">
|
||||
<h2 class="token-page-title">{{ title_long }}</h2>
|
||||
{% if folder_names %}
|
||||
<h3 class="token-folder-title">{{ folder_names | join(', ') }}</h3>
|
||||
{% endif %}
|
||||
<div class="token-qr-wrap mt-3">
|
||||
<img src="/custom_logo/logoW.png" class="token-pdf-logo" alt="Logo">
|
||||
<img src="data:image/png;base64,{{ token_qr_code }}" class="card-img-top qr-code p-3" alt="QR Code for token">
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-text mt-2">
|
||||
<h2 class="card-title">Token-Link:</h2>
|
||||
<div class="token-action-buttons d-flex flex-wrap gap-2 justify-content-center text-center">
|
||||
<a href="{{ token_url }}" class="copy-btn" style="text-decoration: none;" id="tokenLink">Link öffnen</a>
|
||||
<button class="copy-btn" onclick="toClipboard('{{ token_url }}')">Link kopieren</button>
|
||||
<button class="copy-btn" id="printTokenButton" type="button" onclick="printToken()">Drucken</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-text mt-2">
|
||||
<h2 class="mt-3">Gültig bis:</h2>
|
||||
{{ token_valid_to }}
|
||||
</div>
|
||||
<div class="card-text mt-2">
|
||||
<!-- <div class="card-text mt-2">
|
||||
<h2 class="mt-3">Ordner:</h2>
|
||||
{% for folder in token_folder %}
|
||||
{{ folder.foldername }}<br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user