feat(ui): release v1.2.0 with improved installer UX, build tests, and docs update

This commit is contained in:
2026-02-16 21:01:38 +01:00
parent efdb50083e
commit bab54fdf2d
10 changed files with 287 additions and 134 deletions

View File

@@ -1,15 +1,17 @@
# Kantine Wrapper Bookmarklet (v1.7.0)
# Kantine Wrapper Bookmarklet (v1.2.0)
Ein intelligentes Bookmarklet für die Mitarbeiter-Kantine der Bessa App. Dieses Skript erweitert die Standardansicht um eine **Wochenübersicht**, Kostenkontrolle und verbesserte Usability.
## 🚀 Features
* **Wochenübersicht:** Zeigt alle Tage der aktuellen Woche auf einen Blick.
* **Bestell-Countdown:** ⏳ Roter Alarm 1h vor Bestellschluss.
* **Smart Highlights:** 🌟 Markiere deine Favoriten (z.B. "Schnitzel", "Vegetarisch").
* **Bestellstatus:** Farbige Indikatoren für bestellte Menüs.
* **Kostenkontrolle:** Summiert automatisch den Gesamtpreis der Woche.
* **Session Reuse:** Nutzt automatisch eine bestehende Login-Session (Loggt dich automatisch ein).
* **API Fallback:** Prüft die Verbindung und bietet bei Fehlern einen Direktlink zur Originalseite.
* **Menu Badges:** Zeigt Menü-Codes (M1, M2+) direkt im Header.
* **Changelog:** Übersicht über neue Funktionen direkt im Installer.
## 📦 Installation

View File

@@ -108,39 +108,49 @@ cat > "$DIST_DIR/install.html" << INSTALLEOF
</head>
<body>
<h1>🍽️ Kantine Wrapper <span style="font-size:0.5em; opacity:0.6; font-weight:400; vertical-align:middle; margin-left:10px;">$VERSION</span></h1>
<div class="card">
<h2>Changelog</h2>
<div class="changelog-container">
<!-- CHANGELOG_PLACEHOLDER -->
</div>
</div>
<!-- 1. BUTTON (Top Priority) -->
<div class="card" style="text-align: center; border: 2px solid #029AA8;">
<p style="margin-bottom:15px; font-weight:bold;">👇 Diesen Button in die Lesezeichen-Leiste ziehen:</p>
<p><a class="bookmarklet" id="bookmarklet-link" href="#">⏳ Wird generiert...</a></p>
</div>
<div class="card">
<h2>So funktioniert's</h2>
<ol>
<li>Ziehe den Button unten in deine <strong>Lesezeichen-Leiste</strong> (Drag & Drop)</li>
<li>Navigiere zu <a href="https://web.bessa.app/knapp-kantine" style="color:#029AA8">web.bessa.app/knapp-kantine</a></li>
<li>Klicke auf das Lesezeichen <code>Kantine $VERSION</code></li>
</ol>
</div>
<!-- 2. INSTRUCTIONS -->
<div class="card">
<h2>So funktioniert's</h2>
<ol>
<li>Ziehe den Button oben in deine <strong>Lesezeichen-Leiste</strong> (Drag & Drop)</li>
<li>Navigiere zu <a href="https://web.bessa.app/knapp-kantine" style="color:#029AA8">web.bessa.app/knapp-kantine</a></li>
<li>Klicke auf das Lesezeichen <code>Kantine $VERSION</code></li>
</ol>
</div>
<div class="card">
<h2>✨ Features</h2>
<ul>
<li>📅 <strong>Wochenübersicht:</strong> Die ganze Woche auf einen Blick.</li>
<li>💰 <strong>Kostenkontrolle:</strong> Automatische Berechnung der Wochensumme.</li>
<li>🔑 <strong>Auto-Login:</strong> Nutzt deine bestehende Session.</li>
<li>🏷️ <strong>Badges & Status:</strong> Menü-Codes (M1, M2) und Bestellstatus direkt sichtbar.</li>
<li>🛡️ <strong>Offline-Support:</strong> Speichert Menüdaten lokal.</li>
</ul>
<!-- 3. FEATURES -->
<div class="card">
<h2>✨ Features</h2>
<ul>
<li>📅 <strong>Wochenübersicht:</strong> Die ganze Woche auf einen Blick.</li>
<li> <strong>Order Countdown:</strong> Roter Alarm 1h vor Bestellschluss.</li>
<li>🌟 <strong>Smart Highlights:</strong> Markiere deine Favoriten (z.B. "Schnitzel").</li>
<li>💰 <strong>Kostenkontrolle:</strong> Automatische Berechnung der Wochensumme.</li>
<li>🔑 <strong>Auto-Login:</strong> Nutzt deine bestehende Session.</li>
<li>🏷️ <strong>Badges & Status:</strong> Menü-Codes (M1, M2) und Bestellstatus direkt sichtbar.</li>
</ul>
<div style="margin-top: 30px; padding: 15px; background: rgba(233, 69, 96, 0.1); border: 1px solid rgba(233, 69, 96, 0.3); border-radius: 8px; font-size: 0.85em; color: #ddd;">
<div style="margin-top: 30px; padding: 15px; background: rgba(233, 69, 96, 0.1); border: 1px solid rgba(233, 69, 96, 0.3); border-radius: 8px; font-size: 0.85em; color: #ddd;">
<strong>⚠️ Haftungsausschluss:</strong><br>
Die Verwendung dieses Bookmarklets erfolgt auf eigene Verantwortung. Der Entwickler übernimmt keine Haftung für Schäden, Datenverlust oder ungewollte Bestellungen, die durch die Nutzung dieser Software entstehen.
</div>
</div>
<!-- 4. CHANGELOG (Bottom) -->
<div class="card">
<h2>Changelog</h2>
<div class="changelog-container">
<!-- CHANGELOG_PLACEHOLDER -->
</div>
</div>
<p>👇 Diesen Button in die Lesezeichen-Leiste ziehen:</p>
<p><a class="bookmarklet" id="bookmarklet-link" href="#">⏳ Wird generiert...</a></p>
<script>
INSTALLEOF
@@ -168,27 +178,22 @@ import sys, json, urllib.parse
# 1. Read JS and Replace VERSION
js_template = sys.stdin.read()
# We do replacement here in Python to be safe
js = js_template.replace('{{VERSION}}', '$VERSION')
# 2. Prepare CSS
# 2. Prepare CSS for injection via createElement('style')
css = open('$CSS_FILE').read().replace('\n', ' ').replace(' ', ' ')
escaped_css = css.replace('\\\\', '\\\\\\\\').replace(\"'\", \"\\\\'\").replace('\"', '\\\\\"')
# 3. Inject CSS and Update URL
# 3. Update URL
update_url = 'https://htmlpreview.github.io/?https://github.com/TauNeutrino/kantine-overview/blob/main/dist/install.html'
js = js.replace('https://github.com/TauNeutrino/kantine-overview/raw/main/dist/install.html', update_url)
js = js.replace('{{CSS_ESCAPED}}', escaped_css)
# 4. Create Bookmarklet Code
# Wrap in IIFE
bookmarklet_code = 'javascript:(function(){' + js + '})();'
# 4. Create Bookmarklet Code with CSS injection
# Inject CSS via style element (same pattern as bookmarklet-payload.js)
css_injection = \"var s=document.createElement('style');s.textContent='\" + escaped_css + \"';document.head.appendChild(s);\"
bookmarklet_code = 'javascript:(function(){' + css_injection + js + '})();'
# 5. URL Encode the body (keeping javascript: prefix)
# We accept that simple encoding is better.
# But browsers expect encoded URI for href.
# However, for bookmarklet usage, user drags the link.
# If we encode everything, it's safer.
# 5. URL Encode
encoded_code = urllib.parse.quote(bookmarklet_code, safe=':/()!;=+,')
# Output as JSON string for the HTML script to assign to href
@@ -218,3 +223,14 @@ echo ""
echo "=== Build Complete ==="
echo "Files in $DIST_DIR:"
ls -la "$DIST_DIR/"
# === 4. Run build-time tests ===
echo ""
echo "=== Running Build Tests ==="
python3 "$SCRIPT_DIR/test_build.py"
TEST_EXIT=$?
if [ $TEST_EXIT -ne 0 ]; then
echo "❌ Build tests FAILED! See above for details."
exit 1
fi
echo "✅ All build tests passed."

View File

@@ -1,3 +1,8 @@
## v1.2.0 (2026-02-16)
- **Feature**: Bessere UX im Installer (Button oben, Log unten, Features aktualisiert). 💅
- **Tech**: Build-Tests hinzugefügt. 🧪
- **Fix**: Encoding-Probleme final behoben (dank Python Buildlogic). 🐍
## v1.1.2 (2026-02-16)
- **Fix**: Encoding-Problem beim Bookmarklet behoben (URL Malformed Error). 🔗

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

86
dist/install.html vendored

File diff suppressed because one or more lines are too long

View File

@@ -152,17 +152,6 @@ body {
justify-content: center;
}
.weekly-cost {
white-space: nowrap;
font-size: 0.9rem;
font-weight: 600;
color: var(--success-color);
background-color: var(--bg-body);
padding: 0.25rem 0.75rem;
border-radius: 20px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
border: 1px solid var(--border-color);
}
.header-week-title {
font-size: 1.1rem;
@@ -1177,13 +1166,15 @@ body {
color: var(--text-primary);
/* Ensure text remains standard color */
}
/* Update Icon */
.update-icon {
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 8px;
background-color: rgba(16, 185, 129, 0.2); /* Green tint */
background-color: rgba(16, 185, 129, 0.2);
/* Green tint */
color: var(--success-color);
border-radius: 50%;
width: 24px;
@@ -1202,9 +1193,17 @@ body {
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); }
70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
0% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
}
70% {
box-shadow: 0 0 0 6px rgba(16, 185, 129, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
}
}
/* Order Countdown */
@@ -1235,14 +1234,23 @@ body {
}
@keyframes pulse-red {
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
0% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
}
70% {
box-shadow: 0 0 0 6px rgba(239, 68, 68, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
}
}
/* Smart Highlights */
.highlight-glow {
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); /* Blue glow */
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
/* Blue glow */
border: 1px solid rgba(59, 130, 246, 0.8);
background: rgba(59, 130, 246, 0.05);
position: relative;
@@ -1251,14 +1259,16 @@ body {
/* Nav Badge with Count */
.nav-badge.has-highlights {
background-color: var(--card-bg); /* Neutral background */
background-color: var(--bg-card);
/* Neutral background */
color: var(--text-primary);
border: 1px solid var(--border-color);
padding: 2px 6px;
}
.nav-badge .highlight-count {
color: #3b82f6; /* Blue 500 */
color: #3b82f6;
/* Blue 500 */
font-weight: 700;
margin-left: 4px;
}
@@ -1304,7 +1314,7 @@ body {
.input-group input {
flex: 1;
padding: 0.75rem;
background: var(--bg-color);
background: var(--bg-body);
border: 1px solid var(--border-color);
color: var(--text-primary);
border-radius: 8px;
@@ -1313,7 +1323,7 @@ body {
/* Update Banner Enhanced */
.change-summary {
font-size: 0.8rem;
background: rgba(0,0,0,0.1);
background: rgba(0, 0, 0, 0.1);
padding: 0.5rem;
border-radius: 4px;
margin: 0.5rem 0;
@@ -1323,6 +1333,7 @@ body {
max-height: 100px;
overflow-y: auto;
}
.update-content {
display: flex;
flex-direction: column;
@@ -1331,21 +1342,22 @@ body {
}
/* Installer Changelog */
.changelog-container ul {
padding-left: 1.5rem;
.changelog-container ul {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.changelog-container li {
margin-bottom: 0.4rem;
line-height: 1.5;
}
.changelog-container h3 {
margin-top: 1.5rem;
margin-bottom: 0.5rem;
font-size: 1.1em;
color: var(--accent-color);
}
</style>
} </style>
</head>
<body>
<script>
@@ -1417,7 +1429,7 @@ body {
<div class="brand">
<span class="material-icons-round logo-icon">restaurant_menu</span>
<div class="header-left">
<h1>Kantinen Übersicht <small style="font-size: 0.6em; opacity: 0.7; font-weight: 400;">v1.1.2</small></h1>
<h1>Kantinen Übersicht <small style="font-size: 0.6em; opacity: 0.7; font-weight: 400;">v1.2.0</small></h1>
<div id="last-updated-subtitle" class="subtitle"></div>
</div>
</div>
@@ -2750,7 +2762,7 @@ body {
// === Version Check ===
async function checkForUpdates() {
const CurrentVersion = 'v1.1.2';
const CurrentVersion = 'v1.2.0';
const VersionUrl = 'https://raw.githubusercontent.com/TauNeutrino/kantine-overview/main/version.txt';
const InstallerUrl = 'https://htmlpreview.github.io/?https://github.com/TauNeutrino/kantine-overview/blob/main/dist/install.html';

View File

@@ -141,17 +141,6 @@ body {
justify-content: center;
}
.weekly-cost {
white-space: nowrap;
font-size: 0.9rem;
font-weight: 600;
color: var(--success-color);
background-color: var(--bg-body);
padding: 0.25rem 0.75rem;
border-radius: 20px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
border: 1px solid var(--border-color);
}
.header-week-title {
font-size: 1.1rem;
@@ -1166,13 +1155,15 @@ body {
color: var(--text-primary);
/* Ensure text remains standard color */
}
/* Update Icon */
.update-icon {
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 8px;
background-color: rgba(16, 185, 129, 0.2); /* Green tint */
background-color: rgba(16, 185, 129, 0.2);
/* Green tint */
color: var(--success-color);
border-radius: 50%;
width: 24px;
@@ -1191,9 +1182,17 @@ body {
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); }
70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
0% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4);
}
70% {
box-shadow: 0 0 0 6px rgba(16, 185, 129, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
}
}
/* Order Countdown */
@@ -1224,14 +1223,23 @@ body {
}
@keyframes pulse-red {
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
0% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
}
70% {
box-shadow: 0 0 0 6px rgba(239, 68, 68, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
}
}
/* Smart Highlights */
.highlight-glow {
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); /* Blue glow */
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
/* Blue glow */
border: 1px solid rgba(59, 130, 246, 0.8);
background: rgba(59, 130, 246, 0.05);
position: relative;
@@ -1240,14 +1248,16 @@ body {
/* Nav Badge with Count */
.nav-badge.has-highlights {
background-color: var(--card-bg); /* Neutral background */
background-color: var(--bg-card);
/* Neutral background */
color: var(--text-primary);
border: 1px solid var(--border-color);
padding: 2px 6px;
}
.nav-badge .highlight-count {
color: #3b82f6; /* Blue 500 */
color: #3b82f6;
/* Blue 500 */
font-weight: 700;
margin-left: 4px;
}
@@ -1293,7 +1303,7 @@ body {
.input-group input {
flex: 1;
padding: 0.75rem;
background: var(--bg-color);
background: var(--bg-body);
border: 1px solid var(--border-color);
color: var(--text-primary);
border-radius: 8px;
@@ -1302,7 +1312,7 @@ body {
/* Update Banner Enhanced */
.change-summary {
font-size: 0.8rem;
background: rgba(0,0,0,0.1);
background: rgba(0, 0, 0, 0.1);
padding: 0.5rem;
border-radius: 4px;
margin: 0.5rem 0;
@@ -1312,6 +1322,7 @@ body {
max-height: 100px;
overflow-y: auto;
}
.update-content {
display: flex;
flex-direction: column;
@@ -1320,17 +1331,19 @@ body {
}
/* Installer Changelog */
.changelog-container ul {
padding-left: 1.5rem;
.changelog-container ul {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.changelog-container li {
margin-bottom: 0.4rem;
line-height: 1.5;
}
.changelog-container h3 {
margin-top: 1.5rem;
margin-bottom: 0.5rem;
font-size: 1.1em;
color: var(--accent-color);
}
}

89
test_build.py Executable file
View File

@@ -0,0 +1,89 @@
import os
import sys
DIST_DIR = os.path.join(os.path.dirname(__file__), 'dist')
INSTALL_HTML = os.path.join(DIST_DIR, 'install.html')
BOOKMARKLET_TXT = os.path.join(DIST_DIR, 'bookmarklet.txt')
STANDALONE_HTML = os.path.join(DIST_DIR, 'kantine-standalone.html')
def check_file_exists(path, description):
if not os.path.exists(path):
print(f"❌ MISSING: {description} ({path})")
return False
# Check if not empty
if os.path.getsize(path) == 0:
print(f"❌ EMPTY: {description} ({path})")
return False
print(f"✅ FOUND: {description}")
return True
def check_content(path, must_contain=[], must_not_contain=[]):
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
success = True
for item in must_contain:
if item not in content:
print(f"❌ MISSING CONTENT: '{item}' in {os.path.basename(path)}")
success = False
for item in must_not_contain:
if item in content:
print(f"❌ FORBIDDEN CONTENT: '{item}' in {os.path.basename(path)}")
success = False
if success:
print(f"✅ CONTENT VERIFIED: {os.path.basename(path)}")
return success
def main():
print("=== Running Build Tests ===")
# 1. Existence Check
if not all([
check_file_exists(INSTALL_HTML, "Installer HTML"),
check_file_exists(BOOKMARKLET_TXT, "Bookmarklet Text"),
check_file_exists(STANDALONE_HTML, "Standalone HTML")
]):
sys.exit(1)
# 2. Bookmarklet Logic Check
# Must have the CSS injection fix from the external AI
# Must have correct versioning
# Must be properly URL encoded (checking for common issues)
# Read bookmarklet code (decoded mostly by being in txt? No, txt is usually the raw URL)
with open(BOOKMARKLET_TXT, 'r') as f:
bm_code = f.read().strip()
if not bm_code.startswith("javascript:"):
print("❌ Bookmarklet does not start with 'javascript:'")
sys.exit(1)
# Check for placeholder leftovers
if not check_content(BOOKMARKLET_TXT,
must_contain=["document.createElement('style')", "M1", "M2"],
must_not_contain=["{{VERSION}}", "{{CSS_ESCAPED}}"]):
sys.exit(1)
# Check for CSS injection specific logic
if "document.head.appendChild(s)" not in bm_code and "appendChild(s)" not in bm_code: # URL encoded might mask this, strictly checking decoded would be better but simple check first
# Actually bm_code is URL encoded. We should decode it to verify logic.
import urllib.parse
decoded = urllib.parse.unquote(bm_code)
if "document.createElement('style')" not in decoded:
print("❌ CSS Injection logic missing in bookmarklet")
sys.exit(1)
print("✅ CSS Injection logic confirmed")
# 3. Installer Check
if not check_content(INSTALL_HTML,
must_contain=["Kantine Wrapper", "So funktioniert's", "changelog-container"],
must_not_contain=["CHANGELOG_HTML_PLACEHOLDER"]): # If we used that
sys.exit(1)
print("🎉 ALL TESTS PASSED")
sys.exit(0)
if __name__ == "__main__":
main()

View File

@@ -1 +1 @@
v1.1.2
v1.2.0