fix(build): url-encode bookmarklet payload to prevent URI malformed error (v1.1.2)

This commit is contained in:
2026-02-16 19:14:21 +01:00
parent d82dd31bed
commit efdb50083e
8 changed files with 1710 additions and 1587 deletions

View File

@@ -161,18 +161,38 @@ print(html)
") ")
fi fi
# 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"
echo "$JS_CONTENT" | python3 -c " echo "$JS_CONTENT" | python3 -c "
import sys, json import sys, json, urllib.parse
js = sys.stdin.read()
css = open('$CSS_FILE').read().replace('\\n', ' ').replace(' ', ' ') # 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
css = open('$CSS_FILE').read().replace('\n', ' ').replace(' ', ' ')
escaped_css = css.replace('\\\\', '\\\\\\\\').replace(\"'\", \"\\\\'\").replace('\"', '\\\\\"') escaped_css = css.replace('\\\\', '\\\\\\\\').replace(\"'\", \"\\\\'\").replace('\"', '\\\\\"')
# Inject Update URL with htmlpreview # 3. Inject CSS and Update URL
update_url = 'https://htmlpreview.github.io/?https://github.com/TauNeutrino/kantine-overview/blob/main/dist/install.html' 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('https://github.com/TauNeutrino/kantine-overview/raw/main/dist/install.html', update_url)
js = js.replace('{{CSS_ESCAPED}}', escaped_css)
print(json.dumps('javascript:(function(){' + js.replace('{{CSS_ESCAPED}}', escaped_css) + '})();')) # 4. Create Bookmarklet Code
# Wrap in IIFE
bookmarklet_code = 'javascript:(function(){' + 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.
encoded_code = urllib.parse.quote(bookmarklet_code, safe=':/()!;=+,')
# Output as JSON string for the HTML script to assign to href
print(json.dumps(encoded_code) + ';')
" >> "$DIST_DIR/install.html" " >> "$DIST_DIR/install.html"
# Inject Changelog into Installer HTML (Safe Python replace) # Inject Changelog into Installer HTML (Safe Python replace)

View File

@@ -1,3 +1,6 @@
## v1.1.2 (2026-02-16)
- **Fix**: Encoding-Problem beim Bookmarklet behoben (URL Malformed Error). 🔗
## v1.1.1 (2026-02-16) ## v1.1.1 (2026-02-16)
- **Fix**: Kritischer Fehler behoben, der das Laden des Wrappers verhinderte. 🐛 - **Fix**: Kritischer Fehler behoben, der das Laden des Wrappers verhinderte. 🐛

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16
dist/install.html vendored

File diff suppressed because one or more lines are too long

View File

@@ -1417,7 +1417,7 @@ body {
<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="header-left"> <div class="header-left">
<h1>Kantinen Übersicht <small style="font-size: 0.6em; opacity: 0.7; font-weight: 400;">v1.1.1</small></h1> <h1>Kantinen Übersicht <small style="font-size: 0.6em; opacity: 0.7; font-weight: 400;">v1.1.2</small></h1>
<div id="last-updated-subtitle" class="subtitle"></div> <div id="last-updated-subtitle" class="subtitle"></div>
</div> </div>
</div> </div>
@@ -2015,13 +2015,13 @@ body {
// === Highlight Management === // === Highlight Management ===
let highlightTags = JSON.parse(localStorage.getItem('kantine_highlightTags') || '[]'); let highlightTags = JSON.parse(localStorage.getItem('kantine_highlightTags') || '[]');
function saveHighlightTags() { function saveHighlightTags() {
localStorage.setItem('kantine_highlightTags', JSON.stringify(highlightTags)); localStorage.setItem('kantine_highlightTags', JSON.stringify(highlightTags));
renderVisibleWeeks(); // Refresh UI to apply changes renderVisibleWeeks(); // Refresh UI to apply changes
updateNextWeekBadge(); updateNextWeekBadge();
} }
function addHighlightTag(tag) { function addHighlightTag(tag) {
tag = tag.trim().toLowerCase(); tag = tag.trim().toLowerCase();
if (tag && !highlightTags.includes(tag)) { if (tag && !highlightTags.includes(tag)) {
highlightTags.push(tag); highlightTags.push(tag);
@@ -2029,14 +2029,14 @@ function addHighlightTag(tag) {
return true; return true;
} }
return false; return false;
} }
function removeHighlightTag(tag) { function removeHighlightTag(tag) {
highlightTags = highlightTags.filter(t => t !== tag); highlightTags = highlightTags.filter(t => t !== tag);
saveHighlightTags(); saveHighlightTags();
} }
function renderTagsList() { function renderTagsList() {
const list = document.getElementById('tags-list'); const list = document.getElementById('tags-list');
list.innerHTML = ''; list.innerHTML = '';
highlightTags.forEach(tag => { highlightTags.forEach(tag => {
@@ -2053,28 +2053,28 @@ function renderTagsList() {
renderTagsList(); renderTagsList();
}); });
}); });
} }
function checkHighlight(text) { function checkHighlight(text) {
if (!text) return false; if (!text) return false;
text = text.toLowerCase(); text = text.toLowerCase();
return highlightTags.some(tag => text.includes(tag)); return highlightTags.some(tag => text.includes(tag));
} }
// === Local Menu Cache (localStorage) === // === Local Menu Cache (localStorage) ===
const CACHE_KEY = 'kantine_menuCache'; const CACHE_KEY = 'kantine_menuCache';
const CACHE_TS_KEY = 'kantine_menuCacheTs'; const CACHE_TS_KEY = 'kantine_menuCacheTs';
function saveMenuCache() { function saveMenuCache() {
try { try {
localStorage.setItem(CACHE_KEY, JSON.stringify(allWeeks)); localStorage.setItem(CACHE_KEY, JSON.stringify(allWeeks));
localStorage.setItem(CACHE_TS_KEY, new Date().toISOString()); localStorage.setItem(CACHE_TS_KEY, new Date().toISOString());
} catch (e) { } catch (e) {
console.warn('Failed to cache menu data:', e); console.warn('Failed to cache menu data:', e);
} }
} }
function loadMenuCache() { function loadMenuCache() {
try { try {
const cached = localStorage.getItem(CACHE_KEY); const cached = localStorage.getItem(CACHE_KEY);
const cachedTs = localStorage.getItem(CACHE_TS_KEY); const cachedTs = localStorage.getItem(CACHE_TS_KEY);
@@ -2092,10 +2092,10 @@ function loadMenuCache() {
console.warn('Failed to load cached menu:', e); console.warn('Failed to load cached menu:', e);
} }
return false; return false;
} }
// === Menu Data Fetching (Direct from Bessa API) === // === Menu Data Fetching (Direct from Bessa API) ===
async function loadMenuDataFromAPI() { async function loadMenuDataFromAPI() {
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
const progressModal = document.getElementById('progress-modal'); const progressModal = document.getElementById('progress-modal');
const progressFill = document.getElementById('progress-fill'); const progressFill = document.getElementById('progress-fill');
@@ -2288,10 +2288,10 @@ async function loadMenuDataFromAPI() {
} finally { } finally {
loading.classList.add('hidden'); loading.classList.add('hidden');
} }
} }
// === Last Updated Display === // === Last Updated Display ===
function updateLastUpdatedTime(isoTimestamp) { function updateLastUpdatedTime(isoTimestamp) {
const subtitle = document.getElementById('last-updated-subtitle'); const subtitle = document.getElementById('last-updated-subtitle');
if (!isoTimestamp) return; if (!isoTimestamp) return;
try { try {
@@ -2302,10 +2302,10 @@ function updateLastUpdatedTime(isoTimestamp) {
} catch (e) { } catch (e) {
subtitle.textContent = ''; subtitle.textContent = '';
} }
} }
// === Toast Notification === // === Toast Notification ===
function showToast(message, type = 'info') { function showToast(message, type = 'info') {
let container = document.getElementById('toast-container'); let container = document.getElementById('toast-container');
if (!container) { if (!container) {
container = document.createElement('div'); container = document.createElement('div');
@@ -2322,10 +2322,10 @@ function showToast(message, type = 'info') {
toast.classList.remove('show'); toast.classList.remove('show');
setTimeout(() => toast.remove(), 300); setTimeout(() => toast.remove(), 300);
}, 3000); }, 3000);
} }
// === Next Week Badge === // === Next Week Badge ===
function updateNextWeekBadge() { function updateNextWeekBadge() {
const btnNextWeek = document.getElementById('btn-next-week'); const btnNextWeek = document.getElementById('btn-next-week');
let nextWeek = currentWeekNumber + 1; let nextWeek = currentWeekNumber + 1;
let nextYear = currentYear; let nextYear = currentYear;
@@ -2407,10 +2407,10 @@ function updateNextWeekBadge() {
} else if (badge) { } else if (badge) {
badge.remove(); badge.remove();
} }
} }
// === Weekly Cost === // === Weekly Cost ===
function updateWeeklyCost(days) { function updateWeeklyCost(days) {
let totalCost = 0; let totalCost = 0;
if (days && days.length > 0) { if (days && days.length > 0) {
days.forEach(day => { days.forEach(day => {
@@ -2432,10 +2432,10 @@ function updateWeeklyCost(days) {
} else { } else {
costDisplay.classList.add('hidden'); costDisplay.classList.add('hidden');
} }
} }
// === Render Weeks === // === Render Weeks ===
function renderVisibleWeeks() { function renderVisibleWeeks() {
const menuContainer = document.getElementById('menu-container'); const menuContainer = document.getElementById('menu-container');
if (!menuContainer) return; if (!menuContainer) return;
menuContainer.innerHTML = ''; menuContainer.innerHTML = '';
@@ -2493,10 +2493,10 @@ function renderVisibleWeeks() {
menuContainer.appendChild(grid); menuContainer.appendChild(grid);
setTimeout(() => syncMenuItemHeights(grid), 0); setTimeout(() => syncMenuItemHeights(grid), 0);
} }
// === Sync Item Heights === // === Sync Item Heights ===
function syncMenuItemHeights(grid) { function syncMenuItemHeights(grid) {
const cards = grid.querySelectorAll('.menu-card'); const cards = grid.querySelectorAll('.menu-card');
if (cards.length === 0) return; if (cards.length === 0) return;
let maxItems = 0; let maxItems = 0;
@@ -2516,10 +2516,10 @@ function syncMenuItemHeights(grid) {
}); });
itemsAtPos.forEach(item => { item.style.height = `${maxHeight}px`; }); itemsAtPos.forEach(item => { item.style.height = `${maxHeight}px`; });
} }
} }
// === Create Day Card === // === Create Day Card ===
function createDayCard(day) { function createDayCard(day) {
if (!day.items || day.items.length === 0) return null; if (!day.items || day.items.length === 0) return null;
const card = document.createElement('div'); const card = document.createElement('div');
@@ -2746,22 +2746,70 @@ function createDayCard(day) {
card.appendChild(body); card.appendChild(body);
return card; return card;
} }
// === Version Check === // === Version Check ===
async function checkForUpdates() { async function checkForUpdates() {
icon.className = 'update-icon'; const CurrentVersion = 'v1.1.2';
icon.href = url; const VersionUrl = 'https://raw.githubusercontent.com/TauNeutrino/kantine-overview/main/version.txt';
icon.target = '_blank'; const InstallerUrl = 'https://htmlpreview.github.io/?https://github.com/TauNeutrino/kantine-overview/blob/main/dist/install.html';
icon.innerHTML = '🆕'; // User requested icon
icon.title = `Neue Version verfügbar (${newVersion}). Klick für download`;
headerTitle.appendChild(icon); console.log(`[Kantine] Checking for updates... (Current: ${CurrentVersion})`);
showToast(`Update verfügbar: ${newVersion}`, 'info');
}
// === Order Countdown === try {
function updateCountdown() { const response = await fetch(VersionUrl, { cache: 'no-cache' });
if (!response.ok) return;
const remoteVersion = (await response.text()).trim();
if (remoteVersion && remoteVersion !== CurrentVersion) {
console.log(`[Kantine] New version available: ${remoteVersion}`);
// Fetch Changelog content
let changeSummary = '';
try {
const clResp = await fetch('https://raw.githubusercontent.com/TauNeutrino/kantine-overview/main/changelog.md');
if (clResp.ok) {
const clText = await clResp.text();
const match = clText.match(/## (v[^\n]+)\n((?:-[^\n]+\n)+)/);
if (match && match[1].includes(remoteVersion)) {
changeSummary = match[2].replace(/- /g, '• ').trim();
}
}
} catch (e) { console.warn('No changelog', e); }
// Create Banner
const updateBanner = document.createElement('div');
updateBanner.className = 'update-banner';
updateBanner.innerHTML = `
<div class="update-content">
<strong>Update verfügbar: ${remoteVersion}</strong>
${changeSummary ? `<pre class="change-summary">${changeSummary}</pre>` : ''}
<a href="${InstallerUrl}" target="_blank" class="update-link">
<span class="material-icons-round">system_update_alt</span>
Jetzt aktualisieren
</a>
</div>
<button class="icon-btn-small close-update">&times;</button>
`;
document.body.appendChild(updateBanner);
updateBanner.querySelector('.close-update').addEventListener('click', () => updateBanner.remove());
// Highlight Header Icon
const lastUpdatedIcon = document.querySelector('.material-icons-round.logo-icon');
if (lastUpdatedIcon) {
lastUpdatedIcon.style.color = 'var(--accent-color)';
lastUpdatedIcon.parentElement.title = `Update verfügbar: ${remoteVersion}`;
}
}
} catch (error) {
console.warn('[Kantine] Version check failed:', error);
}
}
// === Order Countdown ===
function updateCountdown() {
const now = new Date(); const now = new Date();
const currentDay = now.getDay(); const currentDay = now.getDay();
// Skip weekends (0=Sun, 6=Sat) // Skip weekends (0=Sun, 6=Sat)
@@ -2840,69 +2888,69 @@ function updateCountdown() {
} else { } else {
countdownEl.classList.remove('urgent'); countdownEl.classList.remove('urgent');
} }
} }
function removeCountdown() { function removeCountdown() {
const el = document.getElementById('order-countdown'); const el = document.getElementById('order-countdown');
if (el) el.remove(); if (el) el.remove();
} }
// Update countdown every minute // Update countdown every minute
setInterval(updateCountdown, 60000); setInterval(updateCountdown, 60000);
// Also update on load // Also update on load
setTimeout(updateCountdown, 1000); setTimeout(updateCountdown, 1000);
// === 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()));
const dayNum = d.getUTCDay() || 7; const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum); d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil(((d - yearStart) / 86400000 + 1) / 7); return Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
} }
function getWeekYear(d) { function getWeekYear(d) {
const date = new Date(d.getTime()); const date = new Date(d.getTime());
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
return date.getFullYear(); return date.getFullYear();
} }
function translateDay(englishDay) { function translateDay(englishDay) {
const map = { Monday: 'Montag', Tuesday: 'Dienstag', Wednesday: 'Mittwoch', Thursday: 'Donnerstag', Friday: 'Freitag', Saturday: 'Samstag', Sunday: 'Sonntag' }; const map = { Monday: 'Montag', Tuesday: 'Dienstag', Wednesday: 'Mittwoch', Thursday: 'Donnerstag', Friday: 'Freitag', Saturday: 'Samstag', Sunday: 'Sonntag' };
return map[englishDay] || englishDay; return map[englishDay] || englishDay;
} }
function escapeHtml(text) { function escapeHtml(text) {
const div = document.createElement('div'); const div = document.createElement('div');
div.textContent = text || ''; div.textContent = text || '';
return div.innerHTML; return div.innerHTML;
} }
// === Bootstrap === // === Bootstrap ===
injectUI(); injectUI();
bindEvents(); bindEvents();
updateAuthUI(); updateAuthUI();
cleanupExpiredFlags(); cleanupExpiredFlags();
// Load cached data first for instant UI, then refresh from API // Load cached data first for instant UI, then refresh from API
const hadCache = loadMenuCache(); const hadCache = loadMenuCache();
if (hadCache) { if (hadCache) {
// Hide loading spinner since cache is shown // Hide loading spinner since cache is shown
document.getElementById('loading').classList.add('hidden'); document.getElementById('loading').classList.add('hidden');
} }
loadMenuDataFromAPI(); loadMenuDataFromAPI();
// Auto-start polling if already logged in // Auto-start polling if already logged in
if (authToken) { if (authToken) {
startPolling(); startPolling();
} }
// Check for updates // Check for updates
checkForUpdates(); checkForUpdates();
console.log('Kantine Wrapper loaded ✅'); console.log('Kantine Wrapper loaded ✅');
}) (); })();
// === Error Modal === // === Error Modal ===
function showErrorModal(title, htmlContent, btnText, url) { function showErrorModal(title, htmlContent, btnText, url) {

View File

@@ -664,13 +664,13 @@
// === Highlight Management === // === Highlight Management ===
let highlightTags = JSON.parse(localStorage.getItem('kantine_highlightTags') || '[]'); let highlightTags = JSON.parse(localStorage.getItem('kantine_highlightTags') || '[]');
function saveHighlightTags() { function saveHighlightTags() {
localStorage.setItem('kantine_highlightTags', JSON.stringify(highlightTags)); localStorage.setItem('kantine_highlightTags', JSON.stringify(highlightTags));
renderVisibleWeeks(); // Refresh UI to apply changes renderVisibleWeeks(); // Refresh UI to apply changes
updateNextWeekBadge(); updateNextWeekBadge();
} }
function addHighlightTag(tag) { function addHighlightTag(tag) {
tag = tag.trim().toLowerCase(); tag = tag.trim().toLowerCase();
if (tag && !highlightTags.includes(tag)) { if (tag && !highlightTags.includes(tag)) {
highlightTags.push(tag); highlightTags.push(tag);
@@ -678,14 +678,14 @@ function addHighlightTag(tag) {
return true; return true;
} }
return false; return false;
} }
function removeHighlightTag(tag) { function removeHighlightTag(tag) {
highlightTags = highlightTags.filter(t => t !== tag); highlightTags = highlightTags.filter(t => t !== tag);
saveHighlightTags(); saveHighlightTags();
} }
function renderTagsList() { function renderTagsList() {
const list = document.getElementById('tags-list'); const list = document.getElementById('tags-list');
list.innerHTML = ''; list.innerHTML = '';
highlightTags.forEach(tag => { highlightTags.forEach(tag => {
@@ -702,28 +702,28 @@ function renderTagsList() {
renderTagsList(); renderTagsList();
}); });
}); });
} }
function checkHighlight(text) { function checkHighlight(text) {
if (!text) return false; if (!text) return false;
text = text.toLowerCase(); text = text.toLowerCase();
return highlightTags.some(tag => text.includes(tag)); return highlightTags.some(tag => text.includes(tag));
} }
// === Local Menu Cache (localStorage) === // === Local Menu Cache (localStorage) ===
const CACHE_KEY = 'kantine_menuCache'; const CACHE_KEY = 'kantine_menuCache';
const CACHE_TS_KEY = 'kantine_menuCacheTs'; const CACHE_TS_KEY = 'kantine_menuCacheTs';
function saveMenuCache() { function saveMenuCache() {
try { try {
localStorage.setItem(CACHE_KEY, JSON.stringify(allWeeks)); localStorage.setItem(CACHE_KEY, JSON.stringify(allWeeks));
localStorage.setItem(CACHE_TS_KEY, new Date().toISOString()); localStorage.setItem(CACHE_TS_KEY, new Date().toISOString());
} catch (e) { } catch (e) {
console.warn('Failed to cache menu data:', e); console.warn('Failed to cache menu data:', e);
} }
} }
function loadMenuCache() { function loadMenuCache() {
try { try {
const cached = localStorage.getItem(CACHE_KEY); const cached = localStorage.getItem(CACHE_KEY);
const cachedTs = localStorage.getItem(CACHE_TS_KEY); const cachedTs = localStorage.getItem(CACHE_TS_KEY);
@@ -741,10 +741,10 @@ function loadMenuCache() {
console.warn('Failed to load cached menu:', e); console.warn('Failed to load cached menu:', e);
} }
return false; return false;
} }
// === Menu Data Fetching (Direct from Bessa API) === // === Menu Data Fetching (Direct from Bessa API) ===
async function loadMenuDataFromAPI() { async function loadMenuDataFromAPI() {
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
const progressModal = document.getElementById('progress-modal'); const progressModal = document.getElementById('progress-modal');
const progressFill = document.getElementById('progress-fill'); const progressFill = document.getElementById('progress-fill');
@@ -937,10 +937,10 @@ async function loadMenuDataFromAPI() {
} finally { } finally {
loading.classList.add('hidden'); loading.classList.add('hidden');
} }
} }
// === Last Updated Display === // === Last Updated Display ===
function updateLastUpdatedTime(isoTimestamp) { function updateLastUpdatedTime(isoTimestamp) {
const subtitle = document.getElementById('last-updated-subtitle'); const subtitle = document.getElementById('last-updated-subtitle');
if (!isoTimestamp) return; if (!isoTimestamp) return;
try { try {
@@ -951,10 +951,10 @@ function updateLastUpdatedTime(isoTimestamp) {
} catch (e) { } catch (e) {
subtitle.textContent = ''; subtitle.textContent = '';
} }
} }
// === Toast Notification === // === Toast Notification ===
function showToast(message, type = 'info') { function showToast(message, type = 'info') {
let container = document.getElementById('toast-container'); let container = document.getElementById('toast-container');
if (!container) { if (!container) {
container = document.createElement('div'); container = document.createElement('div');
@@ -971,10 +971,10 @@ function showToast(message, type = 'info') {
toast.classList.remove('show'); toast.classList.remove('show');
setTimeout(() => toast.remove(), 300); setTimeout(() => toast.remove(), 300);
}, 3000); }, 3000);
} }
// === Next Week Badge === // === Next Week Badge ===
function updateNextWeekBadge() { function updateNextWeekBadge() {
const btnNextWeek = document.getElementById('btn-next-week'); const btnNextWeek = document.getElementById('btn-next-week');
let nextWeek = currentWeekNumber + 1; let nextWeek = currentWeekNumber + 1;
let nextYear = currentYear; let nextYear = currentYear;
@@ -1056,10 +1056,10 @@ function updateNextWeekBadge() {
} else if (badge) { } else if (badge) {
badge.remove(); badge.remove();
} }
} }
// === Weekly Cost === // === Weekly Cost ===
function updateWeeklyCost(days) { function updateWeeklyCost(days) {
let totalCost = 0; let totalCost = 0;
if (days && days.length > 0) { if (days && days.length > 0) {
days.forEach(day => { days.forEach(day => {
@@ -1081,10 +1081,10 @@ function updateWeeklyCost(days) {
} else { } else {
costDisplay.classList.add('hidden'); costDisplay.classList.add('hidden');
} }
} }
// === Render Weeks === // === Render Weeks ===
function renderVisibleWeeks() { function renderVisibleWeeks() {
const menuContainer = document.getElementById('menu-container'); const menuContainer = document.getElementById('menu-container');
if (!menuContainer) return; if (!menuContainer) return;
menuContainer.innerHTML = ''; menuContainer.innerHTML = '';
@@ -1142,10 +1142,10 @@ function renderVisibleWeeks() {
menuContainer.appendChild(grid); menuContainer.appendChild(grid);
setTimeout(() => syncMenuItemHeights(grid), 0); setTimeout(() => syncMenuItemHeights(grid), 0);
} }
// === Sync Item Heights === // === Sync Item Heights ===
function syncMenuItemHeights(grid) { function syncMenuItemHeights(grid) {
const cards = grid.querySelectorAll('.menu-card'); const cards = grid.querySelectorAll('.menu-card');
if (cards.length === 0) return; if (cards.length === 0) return;
let maxItems = 0; let maxItems = 0;
@@ -1165,10 +1165,10 @@ function syncMenuItemHeights(grid) {
}); });
itemsAtPos.forEach(item => { item.style.height = `${maxHeight}px`; }); itemsAtPos.forEach(item => { item.style.height = `${maxHeight}px`; });
} }
} }
// === Create Day Card === // === Create Day Card ===
function createDayCard(day) { function createDayCard(day) {
if (!day.items || day.items.length === 0) return null; if (!day.items || day.items.length === 0) return null;
const card = document.createElement('div'); const card = document.createElement('div');
@@ -1395,22 +1395,70 @@ function createDayCard(day) {
card.appendChild(body); card.appendChild(body);
return card; return card;
} }
// === Version Check === // === Version Check ===
async function checkForUpdates() { async function checkForUpdates() {
icon.className = 'update-icon'; const CurrentVersion = '{{VERSION}}';
icon.href = url; const VersionUrl = 'https://raw.githubusercontent.com/TauNeutrino/kantine-overview/main/version.txt';
icon.target = '_blank'; const InstallerUrl = 'https://htmlpreview.github.io/?https://github.com/TauNeutrino/kantine-overview/blob/main/dist/install.html';
icon.innerHTML = '🆕'; // User requested icon
icon.title = `Neue Version verfügbar (${newVersion}). Klick für download`;
headerTitle.appendChild(icon); console.log(`[Kantine] Checking for updates... (Current: ${CurrentVersion})`);
showToast(`Update verfügbar: ${newVersion}`, 'info');
}
// === Order Countdown === try {
function updateCountdown() { const response = await fetch(VersionUrl, { cache: 'no-cache' });
if (!response.ok) return;
const remoteVersion = (await response.text()).trim();
if (remoteVersion && remoteVersion !== CurrentVersion) {
console.log(`[Kantine] New version available: ${remoteVersion}`);
// Fetch Changelog content
let changeSummary = '';
try {
const clResp = await fetch('https://raw.githubusercontent.com/TauNeutrino/kantine-overview/main/changelog.md');
if (clResp.ok) {
const clText = await clResp.text();
const match = clText.match(/## (v[^\n]+)\n((?:-[^\n]+\n)+)/);
if (match && match[1].includes(remoteVersion)) {
changeSummary = match[2].replace(/- /g, '• ').trim();
}
}
} catch (e) { console.warn('No changelog', e); }
// Create Banner
const updateBanner = document.createElement('div');
updateBanner.className = 'update-banner';
updateBanner.innerHTML = `
<div class="update-content">
<strong>Update verfügbar: ${remoteVersion}</strong>
${changeSummary ? `<pre class="change-summary">${changeSummary}</pre>` : ''}
<a href="${InstallerUrl}" target="_blank" class="update-link">
<span class="material-icons-round">system_update_alt</span>
Jetzt aktualisieren
</a>
</div>
<button class="icon-btn-small close-update">&times;</button>
`;
document.body.appendChild(updateBanner);
updateBanner.querySelector('.close-update').addEventListener('click', () => updateBanner.remove());
// Highlight Header Icon
const lastUpdatedIcon = document.querySelector('.material-icons-round.logo-icon');
if (lastUpdatedIcon) {
lastUpdatedIcon.style.color = 'var(--accent-color)';
lastUpdatedIcon.parentElement.title = `Update verfügbar: ${remoteVersion}`;
}
}
} catch (error) {
console.warn('[Kantine] Version check failed:', error);
}
}
// === Order Countdown ===
function updateCountdown() {
const now = new Date(); const now = new Date();
const currentDay = now.getDay(); const currentDay = now.getDay();
// Skip weekends (0=Sun, 6=Sat) // Skip weekends (0=Sun, 6=Sat)
@@ -1489,69 +1537,69 @@ function updateCountdown() {
} else { } else {
countdownEl.classList.remove('urgent'); countdownEl.classList.remove('urgent');
} }
} }
function removeCountdown() { function removeCountdown() {
const el = document.getElementById('order-countdown'); const el = document.getElementById('order-countdown');
if (el) el.remove(); if (el) el.remove();
} }
// Update countdown every minute // Update countdown every minute
setInterval(updateCountdown, 60000); setInterval(updateCountdown, 60000);
// Also update on load // Also update on load
setTimeout(updateCountdown, 1000); setTimeout(updateCountdown, 1000);
// === 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()));
const dayNum = d.getUTCDay() || 7; const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum); d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil(((d - yearStart) / 86400000 + 1) / 7); return Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
} }
function getWeekYear(d) { function getWeekYear(d) {
const date = new Date(d.getTime()); const date = new Date(d.getTime());
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
return date.getFullYear(); return date.getFullYear();
} }
function translateDay(englishDay) { function translateDay(englishDay) {
const map = { Monday: 'Montag', Tuesday: 'Dienstag', Wednesday: 'Mittwoch', Thursday: 'Donnerstag', Friday: 'Freitag', Saturday: 'Samstag', Sunday: 'Sonntag' }; const map = { Monday: 'Montag', Tuesday: 'Dienstag', Wednesday: 'Mittwoch', Thursday: 'Donnerstag', Friday: 'Freitag', Saturday: 'Samstag', Sunday: 'Sonntag' };
return map[englishDay] || englishDay; return map[englishDay] || englishDay;
} }
function escapeHtml(text) { function escapeHtml(text) {
const div = document.createElement('div'); const div = document.createElement('div');
div.textContent = text || ''; div.textContent = text || '';
return div.innerHTML; return div.innerHTML;
} }
// === Bootstrap === // === Bootstrap ===
injectUI(); injectUI();
bindEvents(); bindEvents();
updateAuthUI(); updateAuthUI();
cleanupExpiredFlags(); cleanupExpiredFlags();
// Load cached data first for instant UI, then refresh from API // Load cached data first for instant UI, then refresh from API
const hadCache = loadMenuCache(); const hadCache = loadMenuCache();
if (hadCache) { if (hadCache) {
// Hide loading spinner since cache is shown // Hide loading spinner since cache is shown
document.getElementById('loading').classList.add('hidden'); document.getElementById('loading').classList.add('hidden');
} }
loadMenuDataFromAPI(); loadMenuDataFromAPI();
// Auto-start polling if already logged in // Auto-start polling if already logged in
if (authToken) { if (authToken) {
startPolling(); startPolling();
} }
// Check for updates // Check for updates
checkForUpdates(); checkForUpdates();
console.log('Kantine Wrapper loaded ✅'); console.log('Kantine Wrapper loaded ✅');
}) (); })();
// === Error Modal === // === Error Modal ===
function showErrorModal(title, htmlContent, btnText, url) { function showErrorModal(title, htmlContent, btnText, url) {

View File

@@ -1 +1 @@
v1.1.1 v1.1.2