Compare commits

...

11 Commits

9 changed files with 494 additions and 208 deletions

View File

@@ -8,19 +8,28 @@ DIST_DIR="$SCRIPT_DIR/dist"
CSS_FILE="$SCRIPT_DIR/style.css" CSS_FILE="$SCRIPT_DIR/style.css"
JS_FILE="$SCRIPT_DIR/kantine.js" JS_FILE="$SCRIPT_DIR/kantine.js"
# === VERSION ===
if [ -f "$SCRIPT_DIR/version.txt" ]; then
VERSION=$(cat "$SCRIPT_DIR/version.txt" | tr -d '\n')
else
echo "ERROR: version.txt not found"
exit 1
fi
mkdir -p "$DIST_DIR" mkdir -p "$DIST_DIR"
echo "=== Kantine Bookmarklet Builder ===" echo "=== Kantine Bookmarklet Builder ($VERSION) ==="
# Check files exist # Check files exist
if [ ! -f "$CSS_FILE" ]; then echo "ERROR: $CSS_FILE not found"; exit 1; fi if [ ! -f "$CSS_FILE" ]; then echo "ERROR: $CSS_FILE not found"; exit 1; fi
if [ ! -f "$JS_FILE" ]; then echo "ERROR: $JS_FILE not found"; exit 1; fi if [ ! -f "$JS_FILE" ]; then echo "ERROR: $JS_FILE not found"; exit 1; fi
CSS_CONTENT=$(cat "$CSS_FILE") CSS_CONTENT=$(cat "$CSS_FILE")
JS_CONTENT=$(cat "$JS_FILE") # Inject version into JS
JS_CONTENT=$(cat "$JS_FILE" | sed "s/{{VERSION}}/$VERSION/g")
# === 1. Build standalone HTML (for local testing/dev) === # === 1. Build standalone HTML (for local testing/dev) ===
cat > "$DIST_DIR/kantine-standalone.html" << 'HTMLEOF' cat > "$DIST_DIR/kantine-standalone.html" << HTMLEOF
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@@ -37,7 +46,7 @@ HTMLEOF
# Inject CSS # Inject CSS
cat "$CSS_FILE" >> "$DIST_DIR/kantine-standalone.html" cat "$CSS_FILE" >> "$DIST_DIR/kantine-standalone.html"
cat >> "$DIST_DIR/kantine-standalone.html" << 'HTMLEOF' cat >> "$DIST_DIR/kantine-standalone.html" << HTMLEOF
</style> </style>
</head> </head>
<body> <body>
@@ -45,9 +54,9 @@ cat >> "$DIST_DIR/kantine-standalone.html" << 'HTMLEOF'
HTMLEOF HTMLEOF
# Inject JS # Inject JS
cat "$JS_FILE" >> "$DIST_DIR/kantine-standalone.html" echo "$JS_CONTENT" >> "$DIST_DIR/kantine-standalone.html"
cat >> "$DIST_DIR/kantine-standalone.html" << 'HTMLEOF' cat >> "$DIST_DIR/kantine-standalone.html" << HTMLEOF
</script> </script>
</body> </body>
</html> </html>
@@ -69,7 +78,7 @@ var s=document.createElement('style');
s.textContent='${CSS_ESCAPED}'; s.textContent='${CSS_ESCAPED}';
document.head.appendChild(s); document.head.appendChild(s);
var sc=document.createElement('script'); var sc=document.createElement('script');
sc.textContent=$(cat "$JS_FILE" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null || cat "$JS_FILE" | sed 's/\\/\\\\/g' | sed "s/'/\\\\'/g" | sed 's/"/\\\\"/g' | tr '\n' ' ' | sed 's/^/"/' | sed 's/$/"/'); sc.textContent=$(echo "$JS_CONTENT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null || echo "$JS_CONTENT" | sed 's/\\/\\\\/g' | sed "s/'/\\\\'/g" | sed 's/"/\\\\"/g' | tr '\n' ' ' | sed 's/^/"/' | sed 's/$/"/');
document.head.appendChild(sc); document.head.appendChild(sc);
})(); })();
PAYLOADEOF PAYLOADEOF
@@ -81,107 +90,30 @@ echo "javascript:${BOOKMARKLET_RAW}" > "$DIST_DIR/bookmarklet.txt"
echo "✅ Bookmarklet URL: $DIST_DIR/bookmarklet.txt" echo "✅ Bookmarklet URL: $DIST_DIR/bookmarklet.txt"
# === 3. Create an easy-to-use HTML installer page === # === 3. Create an easy-to-use HTML installer page ===
cat > "$DIST_DIR/install.html" << 'INSTALLEOF' cat > "$DIST_DIR/install.html" << INSTALLEOF
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Kantine Wrapper Installer</title> <title>Kantine Wrapper Installer ($VERSION)</title>
<style> <style>
:root { body { font-family: 'Inter', sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; background: #1a1a2e; color: #eee; }
--knapp-blue: #029AA8; h1 { color: #029AA8; } /* Knapp Teal */
--knapp-teal: #006269; .instructions { background: #16213e; padding: 20px; border-radius: 12px; margin: 20px 0; }
--knapp-yellow: #FFED00; .instructions ol li { margin: 10px 0; }
--text-main: #333333; a.bookmarklet { display: inline-block; background: #029AA8; color: white; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 18px; cursor: grab; }
--bg-body: #f4f6f8; a.bookmarklet:hover { background: #006269; }
--bg-card: #ffffff; code { background: #0f3460; padding: 2px 6px; border-radius: 4px; }
}
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
max-width: 700px;
margin: 40px auto;
padding: 20px;
background: var(--bg-body);
color: var(--text-main);
line-height: 1.6;
}
h1 {
color: var(--knapp-teal);
display: flex;
align-items: center;
gap: 10px;
border-bottom: 2px solid var(--knapp-blue);
padding-bottom: 15px;
}
h2 { color: var(--knapp-blue); font-size: 1.25em; margin-top: 0; }
.instructions {
background: var(--bg-card);
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin: 20px 0;
}
.instructions ol { padding-left: 20px; }
.instructions ol li { margin-bottom: 10px; }
a { color: var(--knapp-blue); text-decoration: none; font-weight: 500; }
a:hover { text-decoration: underline; }
a.bookmarklet {
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--knapp-teal);
color: white;
padding: 15px 30px;
border-radius: 4px;
text-decoration: none;
font-weight: 600;
font-size: 18px;
cursor: grab;
box-shadow: 0 4px 6px rgba(0,98,105,0.2);
transition: all 0.2s;
border-bottom: 3px solid rgba(0,0,0,0.1);
}
a.bookmarklet:hover {
background: var(--knapp-blue);
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(2,154,168,0.25);
}
a.bookmarklet:active {
cursor: grabbing;
}
code {
background: #eef2f5;
padding: 2px 6px;
border-radius: 4px;
color: var(--knapp-teal);
font-family: monospace;
border: 1px solid #dae1e7;
}
ul { padding-left: 20px; color: #555; }
ul li { margin-bottom: 8px; }
.disclaimer {
margin-top: 30px;
padding: 15px;
background: #fffde7; /* Light yellow */
border-left: 4px solid var(--knapp-yellow);
border-radius: 4px;
font-size: 0.85em;
color: #444;
}
</style> </style>
</head> </head>
<body> <body>
<h1>🍽️ Kantine Wrapper</h1> <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="instructions"> <div class="instructions">
<h2>Installation</h2> <h2>Installation</h2>
<ol> <ol>
<li>Ziehe den Button unten in deine <strong>Lesezeichen-Leiste</strong> (Drag & Drop)</li> <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:#e94560">web.bessa.app/knapp-kantine</a></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 Wrapper</code></li> <li>Klicke auf das Lesezeichen <code>Kantine $VERSION</code></li>
</ol> </ol>
<h2>✨ Features</h2> <h2>✨ Features</h2>
@@ -193,7 +125,7 @@ cat > "$DIST_DIR/install.html" << 'INSTALLEOF'
<li>🛡️ <strong>Offline-Support:</strong> Speichert Menüdaten lokal.</li> <li>🛡️ <strong>Offline-Support:</strong> Speichert Menüdaten lokal.</li>
</ul> </ul>
<div class="disclaimer"> <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> <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. 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>
@@ -205,7 +137,7 @@ INSTALLEOF
# Embed the bookmarklet URL inline # Embed the bookmarklet URL inline
echo "document.getElementById('bookmarklet-link').href = " >> "$DIST_DIR/install.html" echo "document.getElementById('bookmarklet-link').href = " >> "$DIST_DIR/install.html"
cat "$JS_FILE" | python3 -c " echo "$JS_CONTENT" | python3 -c "
import sys, json import sys, json
js = sys.stdin.read() js = sys.stdin.read()
css = open('$CSS_FILE').read().replace('\\n', ' ').replace(' ', ' ') css = open('$CSS_FILE').read().replace('\\n', ' ').replace(' ', ' ')
@@ -213,8 +145,8 @@ bmk = '''javascript:(function(){if(window.__KANTINE_LOADED){alert(\"Already load
print(json.dumps(bmk) + ';') print(json.dumps(bmk) + ';')
" 2>/dev/null >> "$DIST_DIR/install.html" || echo "'javascript:alert(\"Build error\")'" >> "$DIST_DIR/install.html" " 2>/dev/null >> "$DIST_DIR/install.html" || echo "'javascript:alert(\"Build error\")'" >> "$DIST_DIR/install.html"
cat >> "$DIST_DIR/install.html" << 'INSTALLEOF' cat >> "$DIST_DIR/install.html" << INSTALLEOF
document.getElementById('bookmarklet-link').textContent = '🍽️ Kantine Wrapper'; document.getElementById('bookmarklet-link').textContent = 'Kantine $VERSION';
</script> </script>
</body> </body>
</html> </html>

150
build-bookmarklet_old.sh Executable file
View File

@@ -0,0 +1,150 @@
#!/bin/bash
# Build script for Kantine Bookmarklet
# Creates a self-contained bookmarklet URL and standalone HTML file
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DIST_DIR="$SCRIPT_DIR/dist"
CSS_FILE="$SCRIPT_DIR/style.css"
JS_FILE="$SCRIPT_DIR/kantine.js"
mkdir -p "$DIST_DIR"
echo "=== Kantine Bookmarklet Builder ==="
# Check files exist
if [ ! -f "$CSS_FILE" ]; then echo "ERROR: $CSS_FILE not found"; exit 1; fi
if [ ! -f "$JS_FILE" ]; then echo "ERROR: $JS_FILE not found"; exit 1; fi
CSS_CONTENT=$(cat "$CSS_FILE")
JS_CONTENT=$(cat "$JS_FILE")
# === 1. Build standalone HTML (for local testing/dev) ===
cat > "$DIST_DIR/kantine-standalone.html" << 'HTMLEOF'
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kantine Weekly Menu (Standalone)</title>
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet">
<style>
HTMLEOF
# Inject CSS
cat "$CSS_FILE" >> "$DIST_DIR/kantine-standalone.html"
cat >> "$DIST_DIR/kantine-standalone.html" << 'HTMLEOF'
</style>
</head>
<body>
<script>
HTMLEOF
# Inject JS
cat "$JS_FILE" >> "$DIST_DIR/kantine-standalone.html"
cat >> "$DIST_DIR/kantine-standalone.html" << 'HTMLEOF'
</script>
</body>
</html>
HTMLEOF
echo "✅ Standalone HTML: $DIST_DIR/kantine-standalone.html"
# === 2. Build bookmarklet (JavaScript URL) ===
# The bookmarklet injects CSS + JS into the current page
# Escape CSS for embedding in JS string
CSS_ESCAPED=$(echo "$CSS_CONTENT" | sed "s/'/\\\\'/g" | tr '\n' ' ' | sed 's/ */ /g')
# Build bookmarklet payload
cat > "$DIST_DIR/bookmarklet-payload.js" << PAYLOADEOF
(function(){
if(window.__KANTINE_LOADED){alert('Kantine Wrapper already loaded!');return;}
var s=document.createElement('style');
s.textContent='${CSS_ESCAPED}';
document.head.appendChild(s);
var sc=document.createElement('script');
sc.textContent=$(cat "$JS_FILE" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null || cat "$JS_FILE" | sed 's/\\/\\\\/g' | sed "s/'/\\\\'/g" | sed 's/"/\\\\"/g' | tr '\n' ' ' | sed 's/^/"/' | sed 's/$/"/');
document.head.appendChild(sc);
})();
PAYLOADEOF
# URL-encode for bookmark
BOOKMARKLET_RAW=$(cat "$DIST_DIR/bookmarklet-payload.js" | tr '\n' ' ' | sed 's/ */ /g')
echo "javascript:${BOOKMARKLET_RAW}" > "$DIST_DIR/bookmarklet.txt"
echo "✅ Bookmarklet URL: $DIST_DIR/bookmarklet.txt"
# === 3. Create an easy-to-use HTML installer page ===
cat > "$DIST_DIR/install.html" << 'INSTALLEOF'
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Kantine Wrapper Installer</title>
<style>
body { font-family: 'Inter', sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; background: #1a1a2e; color: #eee; }
h1 { color: #e94560; }
.instructions { background: #16213e; padding: 20px; border-radius: 12px; margin: 20px 0; }
.instructions ol li { margin: 10px 0; }
a.bookmarklet { display: inline-block; background: #e94560; color: white; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 18px; cursor: grab; }
a.bookmarklet:hover { background: #c73652; }
code { background: #0f3460; padding: 2px 6px; border-radius: 4px; }
</style>
</head>
<body>
<h1>🍽️ Kantine Wrapper</h1>
<div class="instructions">
<h2>Installation</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:#e94560">web.bessa.app/knapp-kantine</a></li>
<li>Klicke auf das Lesezeichen <code>Kantine Wrapper</code></li>
</ol>
<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>
<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>
<p>👇 Diesen Button in die Lesezeichen-Leiste ziehen:</p>
<p><a class="bookmarklet" id="bookmarklet-link" href="#">⏳ Wird generiert...</a></p>
<script>
INSTALLEOF
# Embed the bookmarklet URL inline
echo "document.getElementById('bookmarklet-link').href = " >> "$DIST_DIR/install.html"
cat "$JS_FILE" | python3 -c "
import sys, json
js = sys.stdin.read()
css = open('$CSS_FILE').read().replace('\\n', ' ').replace(' ', ' ')
bmk = '''javascript:(function(){if(window.__KANTINE_LOADED){alert(\"Already loaded\");return;}var s=document.createElement(\"style\");s.textContent=''' + json.dumps(css) + ''';document.head.appendChild(s);var sc=document.createElement(\"script\");sc.textContent=''' + json.dumps(js) + ''';document.head.appendChild(sc);})();'''
print(json.dumps(bmk) + ';')
" 2>/dev/null >> "$DIST_DIR/install.html" || echo "'javascript:alert(\"Build error\")'" >> "$DIST_DIR/install.html"
cat >> "$DIST_DIR/install.html" << 'INSTALLEOF'
document.getElementById('bookmarklet-link').textContent = '🍽️ Kantine Wrapper';
</script>
</body>
</html>
INSTALLEOF
echo "✅ Installer page: $DIST_DIR/install.html"
echo ""
echo "=== Build Complete ==="
echo "Files in $DIST_DIR:"
ls -la "$DIST_DIR/"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

105
dist/install.html vendored

File diff suppressed because one or more lines are too long

View File

@@ -1116,6 +1116,7 @@ body {
box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
} }
} }
/* Day Header Badges */ /* Day Header Badges */
.day-header-left { .day-header-left {
display: flex; display: flex;
@@ -1126,7 +1127,8 @@ body {
.menu-code-badge { .menu-code-badge {
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 700; font-weight: 700;
color: #8b5cf6; /* Violet 500 */ color: #8b5cf6;
/* Violet 500 */
background-color: rgba(139, 92, 246, 0.15); background-color: rgba(139, 92, 246, 0.15);
border: 1px solid rgba(139, 92, 246, 0.3); border: 1px solid rgba(139, 92, 246, 0.3);
padding: 2px 6px; padding: 2px 6px;
@@ -1136,10 +1138,74 @@ body {
} }
/* Detailed Badge Colors */ /* Detailed Badge Colors */
.nav-badge.badge-violet { background-color: #8b5cf6; } .nav-badge.badge-violet {
.nav-badge.badge-green { background-color: var(--success-color); } background-color: #8b5cf6;
.nav-badge.badge-red { background-color: var(--error-color); } }
.nav-badge.badge-blue { background-color: var(--accent-color); }
.nav-badge.badge-green {
background-color: var(--success-color);
}
.nav-badge.badge-red {
background-color: var(--error-color);
}
.nav-badge.badge-blue {
background-color: var(--accent-color);
}
/* Day Header Status Colors (User Request) */
.card-header.header-violet {
background-color: rgba(139, 92, 246, 0.15);
border-bottom: 2px solid #8b5cf6;
}
.card-header.header-green {
background-color: rgba(16, 185, 129, 0.15);
border-bottom: 2px solid var(--success-color);
}
.card-header.header-red {
background-color: rgba(239, 68, 68, 0.15);
border-bottom: 2px solid var(--error-color);
}
.card-header.header-violet .day-name,
.card-header.header-green .day-name,
.card-header.header-red .day-name {
font-weight: 700;
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 */
color: var(--success-color);
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
text-decoration: none;
animation: pulse 2s infinite;
}
.update-icon:hover {
background-color: var(--success-color);
color: white;
transform: scale(1.1);
}
@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); }
}
</style> </style>
</head> </head>
<body> <body>
@@ -1211,8 +1277,8 @@ body {
<div class="header-content"> <div class="header-content">
<div class="brand"> <div class="brand">
<span class="material-icons-round logo-icon">restaurant_menu</span> <span class="material-icons-round logo-icon">restaurant_menu</span>
<div class="brand-text"> <div class="header-left">
<h1>Kantinen Übersicht</h1> <h1>Kantinen Übersicht <small style="font-size: 0.6em; opacity: 0.7; font-weight: 400;">v1.0.3</small></h1>
<div id="last-updated-subtitle" class="subtitle"></div> <div id="last-updated-subtitle" class="subtitle"></div>
</div> </div>
</div> </div>
@@ -2239,6 +2305,33 @@ body {
const badgesHtml = menuBadges.map(code => `<span class="menu-code-badge">${code}</span>`).join(''); const badgesHtml = menuBadges.map(code => `<span class="menu-code-badge">${code}</span>`).join('');
// Determine Day Status for Header Color
// Violet: Has Order
// Green: No Order but Orderable
// Red: No Order and Not Orderable (Locked/Sold Out)
let headerClass = '';
const hasAnyOrder = day.items && day.items.some(item => {
const articleId = item.articleId || parseInt(item.id.split('_')[1]);
const key = `${day.date}_${articleId}`;
return orderMap.has(key) && orderMap.get(key).length > 0;
});
const hasOrderable = day.items && day.items.some(item => {
// Use pre-calculated available flag from loadMenuDataFromAPI calculation
return item.available;
});
if (hasAnyOrder) {
headerClass = 'header-violet';
} else if (hasOrderable && !isPastCutoff) {
headerClass = 'header-green';
} else {
// Red if not orderable (or past cutoff)
headerClass = 'header-red';
}
if (headerClass) header.classList.add(headerClass);
header.innerHTML = ` header.innerHTML = `
<div class="day-header-left"> <div class="day-header-left">
<span class="day-name">${translateDay(day.weekday)}</span> <span class="day-name">${translateDay(day.weekday)}</span>
@@ -2391,6 +2484,50 @@ body {
return card; return card;
} }
// === Version Check ===
async function checkForUpdates() {
const CurrentVersion = 'v1.0.3'; // Injected by build script
const VersionUrl = 'https://raw.githubusercontent.com/TauNeutrino/kantine-overview/main/version.txt';
// Use htmlpreview.github.io to render the HTML directly in browser
const InstallerUrl = 'https://htmlpreview.github.io/?https://github.com/TauNeutrino/kantine-overview/blob/main/dist/install.html';
console.log(`[Kantine] Checking for updates... (Current: ${CurrentVersion})`);
try {
const response = await fetch(VersionUrl, { cache: 'no-cache' });
if (!response.ok) return;
const remoteVersion = (await response.text()).trim();
console.log(`[Kantine] Remote version: ${remoteVersion}`);
if (remoteVersion && remoteVersion !== CurrentVersion) {
// Simple semantic version check or string inequality
// Assuming format v1.0.0
showUpdateIcon(remoteVersion, InstallerUrl);
}
} catch (error) {
console.warn('[Kantine] Version check failed:', error);
}
}
function showUpdateIcon(newVersion, url) {
const headerTitle = document.querySelector('.header-left h1');
if (!headerTitle) return;
// Check if already added
if (headerTitle.querySelector('.update-icon')) return;
const icon = document.createElement('a');
icon.className = 'update-icon';
icon.href = url;
icon.target = '_blank';
icon.innerHTML = '🆕'; // User requested icon
icon.title = `Neue Version verfügbar (${newVersion}). Klick für download`;
headerTitle.appendChild(icon);
showToast(`Update verfügbar: ${newVersion}`, 'info');
}
// === Helpers === // === Helpers ===
function getISOWeek(date) { function getISOWeek(date) {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
@@ -2436,6 +2573,9 @@ body {
startPolling(); startPolling();
} }
// Check for updates
checkForUpdates();
console.log('Kantine Wrapper loaded ✅'); console.log('Kantine Wrapper loaded ✅');
})(); })();

View File

@@ -65,8 +65,8 @@
<div class="header-content"> <div class="header-content">
<div class="brand"> <div class="brand">
<span class="material-icons-round logo-icon">restaurant_menu</span> <span class="material-icons-round logo-icon">restaurant_menu</span>
<div class="brand-text"> <div class="header-left">
<h1>Kantinen Übersicht</h1> <h1>Kantinen Übersicht <small style="font-size: 0.6em; opacity: 0.7; font-weight: 400;">{{VERSION}}</small></h1>
<div id="last-updated-subtitle" class="subtitle"></div> <div id="last-updated-subtitle" class="subtitle"></div>
</div> </div>
</div> </div>
@@ -1093,6 +1093,33 @@
const badgesHtml = menuBadges.map(code => `<span class="menu-code-badge">${code}</span>`).join(''); const badgesHtml = menuBadges.map(code => `<span class="menu-code-badge">${code}</span>`).join('');
// Determine Day Status for Header Color
// Violet: Has Order
// Green: No Order but Orderable
// Red: No Order and Not Orderable (Locked/Sold Out)
let headerClass = '';
const hasAnyOrder = day.items && day.items.some(item => {
const articleId = item.articleId || parseInt(item.id.split('_')[1]);
const key = `${day.date}_${articleId}`;
return orderMap.has(key) && orderMap.get(key).length > 0;
});
const hasOrderable = day.items && day.items.some(item => {
// Use pre-calculated available flag from loadMenuDataFromAPI calculation
return item.available;
});
if (hasAnyOrder) {
headerClass = 'header-violet';
} else if (hasOrderable && !isPastCutoff) {
headerClass = 'header-green';
} else {
// Red if not orderable (or past cutoff)
headerClass = 'header-red';
}
if (headerClass) header.classList.add(headerClass);
header.innerHTML = ` header.innerHTML = `
<div class="day-header-left"> <div class="day-header-left">
<span class="day-name">${translateDay(day.weekday)}</span> <span class="day-name">${translateDay(day.weekday)}</span>
@@ -1245,6 +1272,50 @@
return card; return card;
} }
// === Version Check ===
async function checkForUpdates() {
const CurrentVersion = '{{VERSION}}'; // Injected by build script
const VersionUrl = 'https://raw.githubusercontent.com/TauNeutrino/kantine-overview/main/version.txt';
// Use htmlpreview.github.io to render the HTML directly in browser
const InstallerUrl = 'https://htmlpreview.github.io/?https://github.com/TauNeutrino/kantine-overview/blob/main/dist/install.html';
console.log(`[Kantine] Checking for updates... (Current: ${CurrentVersion})`);
try {
const response = await fetch(VersionUrl, { cache: 'no-cache' });
if (!response.ok) return;
const remoteVersion = (await response.text()).trim();
console.log(`[Kantine] Remote version: ${remoteVersion}`);
if (remoteVersion && remoteVersion !== CurrentVersion) {
// Simple semantic version check or string inequality
// Assuming format v1.0.0
showUpdateIcon(remoteVersion, InstallerUrl);
}
} catch (error) {
console.warn('[Kantine] Version check failed:', error);
}
}
function showUpdateIcon(newVersion, url) {
const headerTitle = document.querySelector('.header-left h1');
if (!headerTitle) return;
// Check if already added
if (headerTitle.querySelector('.update-icon')) return;
const icon = document.createElement('a');
icon.className = 'update-icon';
icon.href = url;
icon.target = '_blank';
icon.innerHTML = '🆕'; // User requested icon
icon.title = `Neue Version verfügbar (${newVersion}). Klick für download`;
headerTitle.appendChild(icon);
showToast(`Update verfügbar: ${newVersion}`, 'info');
}
// === Helpers === // === Helpers ===
function getISOWeek(date) { function getISOWeek(date) {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
@@ -1290,6 +1361,9 @@
startPolling(); startPolling();
} }
// Check for updates
checkForUpdates();
console.log('Kantine Wrapper loaded ✅'); console.log('Kantine Wrapper loaded ✅');
})(); })();

View File

@@ -1105,6 +1105,7 @@ body {
box-shadow: 0 0 10px rgba(16, 185, 129, 0.3); box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
} }
} }
/* Day Header Badges */ /* Day Header Badges */
.day-header-left { .day-header-left {
display: flex; display: flex;
@@ -1115,7 +1116,8 @@ body {
.menu-code-badge { .menu-code-badge {
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 700; font-weight: 700;
color: #8b5cf6; /* Violet 500 */ color: #8b5cf6;
/* Violet 500 */
background-color: rgba(139, 92, 246, 0.15); background-color: rgba(139, 92, 246, 0.15);
border: 1px solid rgba(139, 92, 246, 0.3); border: 1px solid rgba(139, 92, 246, 0.3);
padding: 2px 6px; padding: 2px 6px;
@@ -1125,7 +1127,71 @@ body {
} }
/* Detailed Badge Colors */ /* Detailed Badge Colors */
.nav-badge.badge-violet { background-color: #8b5cf6; } .nav-badge.badge-violet {
.nav-badge.badge-green { background-color: var(--success-color); } background-color: #8b5cf6;
.nav-badge.badge-red { background-color: var(--error-color); } }
.nav-badge.badge-blue { background-color: var(--accent-color); }
.nav-badge.badge-green {
background-color: var(--success-color);
}
.nav-badge.badge-red {
background-color: var(--error-color);
}
.nav-badge.badge-blue {
background-color: var(--accent-color);
}
/* Day Header Status Colors (User Request) */
.card-header.header-violet {
background-color: rgba(139, 92, 246, 0.15);
border-bottom: 2px solid #8b5cf6;
}
.card-header.header-green {
background-color: rgba(16, 185, 129, 0.15);
border-bottom: 2px solid var(--success-color);
}
.card-header.header-red {
background-color: rgba(239, 68, 68, 0.15);
border-bottom: 2px solid var(--error-color);
}
.card-header.header-violet .day-name,
.card-header.header-green .day-name,
.card-header.header-red .day-name {
font-weight: 700;
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 */
color: var(--success-color);
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
text-decoration: none;
animation: pulse 2s infinite;
}
.update-icon:hover {
background-color: var(--success-color);
color: white;
transform: scale(1.1);
}
@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); }
}

1
version.txt Executable file
View File

@@ -0,0 +1 @@
v1.0.3