fix(build): url-encode bookmarklet payload to prevent URI malformed error (v1.1.2)
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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. 🐛
|
||||||
|
|
||||||
|
|||||||
2
dist/bookmarklet-payload.js
vendored
2
dist/bookmarklet-payload.js
vendored
File diff suppressed because one or more lines are too long
2
dist/bookmarklet.txt
vendored
2
dist/bookmarklet.txt
vendored
File diff suppressed because one or more lines are too long
16
dist/install.html
vendored
16
dist/install.html
vendored
File diff suppressed because one or more lines are too long
222
dist/kantine-standalone.html
vendored
222
dist/kantine-standalone.html
vendored
@@ -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">×</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) {
|
||||||
|
|||||||
220
kantine.js
220
kantine.js
@@ -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">×</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) {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v1.1.1
|
v1.1.2
|
||||||
|
|||||||
Reference in New Issue
Block a user