feat: GitHub Release Management v1.3.0 - Version menu, dev-mode, downgrade support
This commit is contained in:
@@ -25,8 +25,19 @@ 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")
|
||||||
# Inject version into JS
|
|
||||||
JS_CONTENT=$(cat "$JS_FILE" | sed "s/{{VERSION}}/$VERSION/g")
|
# Read GitHub PAT (optional, for higher API rate limits)
|
||||||
|
PAT_FILE="$SCRIPT_DIR/github_pat.txt"
|
||||||
|
if [ -f "$PAT_FILE" ]; then
|
||||||
|
GITHUB_PAT=$(cat "$PAT_FILE" | tr -d '\n')
|
||||||
|
echo "✅ GitHub PAT loaded"
|
||||||
|
else
|
||||||
|
GITHUB_PAT=""
|
||||||
|
echo "⚠️ No github_pat.txt found (API rate limit: 60/h)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Inject version and PAT into JS (use pipe delimiter for sed to avoid special char issues)
|
||||||
|
JS_CONTENT=$(cat "$JS_FILE" | sed "s|{{VERSION}}|$VERSION|g" | sed "s|{{GITHUB_PAT}}|$GITHUB_PAT|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
|
||||||
@@ -265,3 +276,13 @@ if [ $TEST_EXIT -ne 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "✅ All build tests passed."
|
echo "✅ All build tests passed."
|
||||||
|
|
||||||
|
# === 5. Auto-tag version ===
|
||||||
|
echo ""
|
||||||
|
echo "=== Tagging $VERSION ==="
|
||||||
|
if git rev-parse "$VERSION" >/dev/null 2>&1; then
|
||||||
|
echo "ℹ️ Tag $VERSION already exists, skipping."
|
||||||
|
else
|
||||||
|
git tag "$VERSION"
|
||||||
|
echo "✅ Created tag: $VERSION"
|
||||||
|
fi
|
||||||
|
|||||||
10
changelog.md
10
changelog.md
@@ -1,5 +1,13 @@
|
|||||||
|
## v1.3.0 (2026-02-16)
|
||||||
|
- **Feature**: GitHub Release Management 📦
|
||||||
|
- Version-Menü: Klick auf Versionsnummer zeigt alle verfügbaren Versionen
|
||||||
|
- Dev-Mode Toggle: Zwischen Releases (stabil) und Tags (dev) wechseln
|
||||||
|
- Downgrade-Support: Jede Version hat einen eigenen Installer-Link
|
||||||
|
- Update-Check nutzt jetzt die GitHub API statt `version.txt`
|
||||||
|
- GitHub PAT für höheres API-Rate-Limit (5000/h)
|
||||||
|
- SemVer-Check: Update-Icon nur bei wirklich neuerer Version
|
||||||
|
|
||||||
## v1.2.9 (2026-02-16)
|
## v1.2.9 (2026-02-16)
|
||||||
- **Fix**: Update-Benachrichtigung erscheint nur noch, wenn die GitHub-Version wirklich *neuer* ist (SemVer-Check). Kein Icon mehr bei gleichen oder älteren Versionen. ✅
|
|
||||||
|
|
||||||
## v1.2.8 (2026-02-16)
|
## v1.2.8 (2026-02-16)
|
||||||
- **Debug**: Weiteres Logging (Fetch-Status, Start-Log) zur Fehlersuche. 🔎
|
- **Debug**: Weiteres Logging (Fetch-Status, Start-Log) zur Fehlersuche. 🔎
|
||||||
|
|||||||
4
dist/bookmarklet-payload.js
vendored
4
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
24
dist/install.html
vendored
24
dist/install.html
vendored
File diff suppressed because one or more lines are too long
354
dist/kantine-standalone.html
vendored
354
dist/kantine-standalone.html
vendored
@@ -1423,6 +1423,128 @@ body {
|
|||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Version Menu === */
|
||||||
|
.version-tag {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s ease, text-decoration 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-tag:hover {
|
||||||
|
opacity: 1 !important;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
max-height: 350px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item:hover {
|
||||||
|
background: rgba(100, 116, 139, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item.current {
|
||||||
|
background: rgba(2, 154, 168, 0.1);
|
||||||
|
border: 1px solid rgba(2, 154, 168, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .version-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .version-item.current {
|
||||||
|
background: rgba(96, 165, 250, 0.12);
|
||||||
|
border: 1px solid rgba(96, 165, 250, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-current {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--success-color);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(5, 150, 105, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-new {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #029aa8;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(2, 154, 168, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .badge-new {
|
||||||
|
color: #60a5fa;
|
||||||
|
background: rgba(96, 165, 250, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-link {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(2, 154, 168, 0.1);
|
||||||
|
color: #029aa8;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid rgba(2, 154, 168, 0.25);
|
||||||
|
transition: all 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-link:hover {
|
||||||
|
background: rgba(2, 154, 168, 0.2);
|
||||||
|
border-color: rgba(2, 154, 168, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .install-link {
|
||||||
|
color: #60a5fa;
|
||||||
|
background: rgba(96, 165, 250, 0.12);
|
||||||
|
border: 1px solid rgba(96, 165, 250, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .install-link:hover {
|
||||||
|
background: rgba(96, 165, 250, 0.2);
|
||||||
|
border-color: rgba(96, 165, 250, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-toggle {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(100, 116, 139, 0.05);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-toggle input[type="checkbox"] {
|
||||||
|
accent-color: #029aa8;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .dev-toggle input[type="checkbox"] {
|
||||||
|
accent-color: #60a5fa;
|
||||||
} </style>
|
} </style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -1633,6 +1755,12 @@ body {
|
|||||||
const MENU_ID = 7;
|
const MENU_ID = 7;
|
||||||
const POLL_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
const POLL_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
||||||
|
|
||||||
|
// === GitHub Release Management ===
|
||||||
|
const GITHUB_REPO = 'TauNeutrino/kantine-overview';
|
||||||
|
const GITHUB_API = `https://api.github.com/repos/${GITHUB_REPO}`;
|
||||||
|
const GITHUB_PAT = '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'; // Injected by build script
|
||||||
|
const INSTALLER_BASE = `https://htmlpreview.github.io/?https://github.com/${GITHUB_REPO}/blob`;
|
||||||
|
|
||||||
// === State ===
|
// === State ===
|
||||||
let allWeeks = [];
|
let allWeeks = [];
|
||||||
let currentWeekNumber = getISOWeek(new Date());
|
let currentWeekNumber = getISOWeek(new Date());
|
||||||
@@ -1680,7 +1808,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.2.9</small></h1>
|
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.3.0</small></h1>
|
||||||
<div id="last-updated-subtitle" class="subtitle"></div>
|
<div id="last-updated-subtitle" class="subtitle"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1782,6 +1910,31 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="version-modal" class="modal hidden">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>📦 Versionen</h2>
|
||||||
|
<button id="btn-version-close" class="icon-btn" aria-label="Close">
|
||||||
|
<span class="material-icons-round">close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div style="margin-bottom: 1rem;">
|
||||||
|
<strong>Aktuell:</strong> <span id="version-current">v1.3.0</span>
|
||||||
|
</div>
|
||||||
|
<div class="dev-toggle">
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
|
||||||
|
<input type="checkbox" id="dev-mode-toggle">
|
||||||
|
<span>Dev-Mode (alle Tags anzeigen)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div id="version-list-container" style="margin-top:1rem;">
|
||||||
|
<p style="color:var(--text-secondary);">Lade Versionen...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<div id="last-updated-banner" class="banner hidden">
|
<div id="last-updated-banner" class="banner hidden">
|
||||||
<span class="material-icons-round">update</span>
|
<span class="material-icons-round">update</span>
|
||||||
@@ -1833,6 +1986,29 @@ body {
|
|||||||
if (e.target === highlightsModal) highlightsModal.classList.add('hidden');
|
if (e.target === highlightsModal) highlightsModal.classList.add('hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Version Menu
|
||||||
|
const versionTag = document.querySelector('.version-tag');
|
||||||
|
const versionModal = document.getElementById('version-modal');
|
||||||
|
const btnVersionClose = document.getElementById('btn-version-close');
|
||||||
|
|
||||||
|
if (versionTag) {
|
||||||
|
versionTag.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
openVersionMenu();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnVersionClose) {
|
||||||
|
btnVersionClose.addEventListener('click', () => {
|
||||||
|
versionModal.classList.add('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('click', (e) => {
|
||||||
|
if (e.target === versionModal) versionModal.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
btnAddTag.addEventListener('click', () => {
|
btnAddTag.addEventListener('click', () => {
|
||||||
const tag = tagInput.value;
|
const tag = tagInput.value;
|
||||||
if (addHighlightTag(tag)) {
|
if (addHighlightTag(tag)) {
|
||||||
@@ -3025,54 +3201,81 @@ body {
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Version Check (periodic, every hour) ===
|
// === GitHub Release Management ===
|
||||||
|
|
||||||
|
// Semver comparison: returns true if remote > local
|
||||||
|
function isNewer(remote, local) {
|
||||||
|
if (!remote || !local) return false;
|
||||||
|
const r = remote.replace(/^v/, '').split('.').map(Number);
|
||||||
|
const l = local.replace(/^v/, '').split('.').map(Number);
|
||||||
|
for (let i = 0; i < Math.max(r.length, l.length); i++) {
|
||||||
|
if ((r[i] || 0) > (l[i] || 0)) return true;
|
||||||
|
if ((r[i] || 0) < (l[i] || 0)) return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitHub API headers (with optional PAT for higher rate limits)
|
||||||
|
function githubHeaders() {
|
||||||
|
const h = { 'Accept': 'application/vnd.github.v3+json' };
|
||||||
|
if (GITHUB_PAT && GITHUB_PAT !== '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') {
|
||||||
|
h['Authorization'] = `token ${GITHUB_PAT}`;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch versions from GitHub (releases or tags)
|
||||||
|
async function fetchVersions(devMode) {
|
||||||
|
const endpoint = devMode
|
||||||
|
? `${GITHUB_API}/tags?per_page=20`
|
||||||
|
: `${GITHUB_API}/releases?per_page=20`;
|
||||||
|
|
||||||
|
const resp = await fetch(endpoint, { headers: githubHeaders() });
|
||||||
|
if (!resp.ok) throw new Error(`GitHub API ${resp.status}`);
|
||||||
|
const data = await resp.json();
|
||||||
|
|
||||||
|
// Normalize to common format: { tag, name, url, body }
|
||||||
|
return data.map(item => {
|
||||||
|
const tag = devMode ? item.name : item.tag_name;
|
||||||
|
return {
|
||||||
|
tag,
|
||||||
|
name: devMode ? tag : (item.name || tag),
|
||||||
|
url: `${INSTALLER_BASE}/${tag}/dist/install.html`,
|
||||||
|
body: item.body || ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periodic update check (runs on init + every hour)
|
||||||
async function checkForUpdates() {
|
async function checkForUpdates() {
|
||||||
console.log('[Kantine] Starting update check...');
|
const currentVersion = 'v1.3.0';
|
||||||
const currentVersion = 'v1.2.9';
|
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
|
||||||
const versionUrl = 'https://raw.githubusercontent.com/TauNeutrino/kantine-overview/main/version.txt';
|
|
||||||
const installerUrl = 'https://htmlpreview.github.io/?https://github.com/TauNeutrino/kantine-overview/blob/main/dist/install.html';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[Kantine] Fetching ${versionUrl}...`);
|
const versions = await fetchVersions(devMode);
|
||||||
const resp = await fetch(versionUrl, { cache: 'no-cache' });
|
if (!versions.length) return;
|
||||||
console.log(`[Kantine] Fetch status: ${resp.status}`);
|
|
||||||
|
|
||||||
if (!resp.ok) {
|
// Cache for version menu
|
||||||
console.warn(`[Kantine] Version Check HTTP Error: ${resp.status}`);
|
localStorage.setItem('kantine_version_cache', JSON.stringify({
|
||||||
return;
|
timestamp: Date.now(), devMode, versions
|
||||||
}
|
}));
|
||||||
const remoteVersion = (await resp.text()).trim();
|
|
||||||
|
|
||||||
console.log(`[Kantine] Version Check: Local [${currentVersion}] vs Remote [${remoteVersion}]`);
|
const latest = versions[0].tag;
|
||||||
|
console.log(`[Kantine] Version Check: Local [${currentVersion}] vs Latest [${latest}] (${devMode ? 'dev' : 'stable'})`);
|
||||||
|
|
||||||
// Check if remote is NEWER (simple semver check)
|
if (!isNewer(latest, currentVersion)) return;
|
||||||
const isNewer = (remote, local) => {
|
|
||||||
if (!remote || !local) return false;
|
|
||||||
const r = remote.replace(/^v/, '').split('.').map(Number);
|
|
||||||
const l = local.replace(/^v/, '').split('.').map(Number);
|
|
||||||
for (let i = 0; i < Math.max(r.length, l.length); i++) {
|
|
||||||
if ((r[i] || 0) > (l[i] || 0)) return true;
|
|
||||||
if ((r[i] || 0) < (l[i] || 0)) return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isNewer(remoteVersion, currentVersion)) {
|
console.log(`[Kantine] Update verfügbar: ${latest}`);
|
||||||
console.log('[Kantine] No update needed (Remote is not newer).');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[Kantine] Update verfügbar: ${remoteVersion}`);
|
|
||||||
|
|
||||||
// Show 🆕 icon in header (only once)
|
// Show 🆕 icon in header (only once)
|
||||||
const headerTitle = document.querySelector('.header-left h1');
|
const headerTitle = document.querySelector('.header-left h1');
|
||||||
if (headerTitle && !headerTitle.querySelector('.update-icon')) {
|
if (headerTitle && !headerTitle.querySelector('.update-icon')) {
|
||||||
const icon = document.createElement('a');
|
const icon = document.createElement('a');
|
||||||
icon.className = 'update-icon';
|
icon.className = 'update-icon';
|
||||||
icon.href = installerUrl;
|
icon.href = versions[0].url;
|
||||||
icon.target = '_blank';
|
icon.target = '_blank';
|
||||||
icon.innerHTML = '🆕';
|
icon.innerHTML = '🆕';
|
||||||
icon.title = `Update verfügbar: ${remoteVersion} — Klick zum Installieren`;
|
icon.title = `Update: ${latest} — Klick zum Installieren`;
|
||||||
icon.style.cssText = 'margin-left:8px;font-size:1em;text-decoration:none;cursor:pointer;vertical-align:middle;';
|
icon.style.cssText = 'margin-left:8px;font-size:1em;text-decoration:none;cursor:pointer;vertical-align:middle;';
|
||||||
headerTitle.appendChild(icon);
|
headerTitle.appendChild(icon);
|
||||||
}
|
}
|
||||||
@@ -3081,6 +3284,89 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open Version Menu modal
|
||||||
|
function openVersionMenu() {
|
||||||
|
const modal = document.getElementById('version-modal');
|
||||||
|
const container = document.getElementById('version-list-container');
|
||||||
|
const devToggle = document.getElementById('dev-mode-toggle');
|
||||||
|
const currentVersion = 'v1.3.0';
|
||||||
|
|
||||||
|
if (!modal) return;
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Set current version display
|
||||||
|
const cur = document.getElementById('version-current');
|
||||||
|
if (cur) cur.textContent = currentVersion;
|
||||||
|
|
||||||
|
// Init dev toggle
|
||||||
|
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
|
||||||
|
devToggle.checked = devMode;
|
||||||
|
|
||||||
|
// Load versions (from cache or fresh)
|
||||||
|
async function loadVersions(forceRefresh) {
|
||||||
|
const dm = devToggle.checked;
|
||||||
|
container.innerHTML = '<p style="color:var(--text-secondary);">Lade Versionen...</p>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
let versions;
|
||||||
|
const cached = JSON.parse(localStorage.getItem('kantine_version_cache') || 'null');
|
||||||
|
if (!forceRefresh && cached && cached.devMode === dm && (Date.now() - cached.timestamp < 3600000)) {
|
||||||
|
versions = cached.versions;
|
||||||
|
} else {
|
||||||
|
versions = await fetchVersions(dm);
|
||||||
|
localStorage.setItem('kantine_version_cache', JSON.stringify({
|
||||||
|
timestamp: Date.now(), devMode: dm, versions
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!versions.length) {
|
||||||
|
container.innerHTML = '<p style="color:var(--text-secondary);">Keine Versionen gefunden.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = '<ul class="version-list"></ul>';
|
||||||
|
const list = container.querySelector('.version-list');
|
||||||
|
|
||||||
|
versions.forEach(v => {
|
||||||
|
const isCurrent = v.tag === currentVersion;
|
||||||
|
const isNew = isNewer(v.tag, currentVersion);
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'version-item' + (isCurrent ? ' current' : '');
|
||||||
|
|
||||||
|
let badge = '';
|
||||||
|
if (isCurrent) badge = '<span class="badge-current">✓ Installiert</span>';
|
||||||
|
else if (isNew) badge = '<span class="badge-new">⬆ Neu!</span>';
|
||||||
|
|
||||||
|
let action = '';
|
||||||
|
if (!isCurrent) {
|
||||||
|
action = `<a href="${v.url}" target="_blank" class="install-link" title="${v.tag} installieren">Installieren</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="version-info">
|
||||||
|
<strong>${v.tag}</strong>
|
||||||
|
${badge}
|
||||||
|
</div>
|
||||||
|
${action}
|
||||||
|
`;
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
container.innerHTML = `<p style="color:#e94560;">Fehler: ${e.message}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadVersions(false);
|
||||||
|
|
||||||
|
// Dev toggle handler
|
||||||
|
devToggle.onchange = () => {
|
||||||
|
localStorage.setItem('kantine_dev_mode', devToggle.checked);
|
||||||
|
// Clear cache to force refresh when mode changes
|
||||||
|
localStorage.removeItem('kantine_version_cache');
|
||||||
|
loadVersions(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// === Order Countdown ===
|
// === Order Countdown ===
|
||||||
function updateCountdown() {
|
function updateCountdown() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
1
github_pat.txt
Executable file
1
github_pat.txt
Executable file
@@ -0,0 +1 @@
|
|||||||
|
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
|
||||||
230
kantine.js
230
kantine.js
@@ -19,6 +19,12 @@
|
|||||||
const MENU_ID = 7;
|
const MENU_ID = 7;
|
||||||
const POLL_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
const POLL_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
||||||
|
|
||||||
|
// === GitHub Release Management ===
|
||||||
|
const GITHUB_REPO = 'TauNeutrino/kantine-overview';
|
||||||
|
const GITHUB_API = `https://api.github.com/repos/${GITHUB_REPO}`;
|
||||||
|
const GITHUB_PAT = '{{GITHUB_PAT}}'; // Injected by build script
|
||||||
|
const INSTALLER_BASE = `https://htmlpreview.github.io/?https://github.com/${GITHUB_REPO}/blob`;
|
||||||
|
|
||||||
// === State ===
|
// === State ===
|
||||||
let allWeeks = [];
|
let allWeeks = [];
|
||||||
let currentWeekNumber = getISOWeek(new Date());
|
let currentWeekNumber = getISOWeek(new Date());
|
||||||
@@ -66,7 +72,7 @@
|
|||||||
<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;">{{VERSION}}</small></h1>
|
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">{{VERSION}}</small></h1>
|
||||||
<div id="last-updated-subtitle" class="subtitle"></div>
|
<div id="last-updated-subtitle" class="subtitle"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,6 +174,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="version-modal" class="modal hidden">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>📦 Versionen</h2>
|
||||||
|
<button id="btn-version-close" class="icon-btn" aria-label="Close">
|
||||||
|
<span class="material-icons-round">close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div style="margin-bottom: 1rem;">
|
||||||
|
<strong>Aktuell:</strong> <span id="version-current">{{VERSION}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dev-toggle">
|
||||||
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
|
||||||
|
<input type="checkbox" id="dev-mode-toggle">
|
||||||
|
<span>Dev-Mode (alle Tags anzeigen)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div id="version-list-container" style="margin-top:1rem;">
|
||||||
|
<p style="color:var(--text-secondary);">Lade Versionen...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<div id="last-updated-banner" class="banner hidden">
|
<div id="last-updated-banner" class="banner hidden">
|
||||||
<span class="material-icons-round">update</span>
|
<span class="material-icons-round">update</span>
|
||||||
@@ -219,6 +250,29 @@
|
|||||||
if (e.target === highlightsModal) highlightsModal.classList.add('hidden');
|
if (e.target === highlightsModal) highlightsModal.classList.add('hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Version Menu
|
||||||
|
const versionTag = document.querySelector('.version-tag');
|
||||||
|
const versionModal = document.getElementById('version-modal');
|
||||||
|
const btnVersionClose = document.getElementById('btn-version-close');
|
||||||
|
|
||||||
|
if (versionTag) {
|
||||||
|
versionTag.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
openVersionMenu();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnVersionClose) {
|
||||||
|
btnVersionClose.addEventListener('click', () => {
|
||||||
|
versionModal.classList.add('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('click', (e) => {
|
||||||
|
if (e.target === versionModal) versionModal.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
btnAddTag.addEventListener('click', () => {
|
btnAddTag.addEventListener('click', () => {
|
||||||
const tag = tagInput.value;
|
const tag = tagInput.value;
|
||||||
if (addHighlightTag(tag)) {
|
if (addHighlightTag(tag)) {
|
||||||
@@ -1411,54 +1465,81 @@
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Version Check (periodic, every hour) ===
|
// === GitHub Release Management ===
|
||||||
|
|
||||||
|
// Semver comparison: returns true if remote > local
|
||||||
|
function isNewer(remote, local) {
|
||||||
|
if (!remote || !local) return false;
|
||||||
|
const r = remote.replace(/^v/, '').split('.').map(Number);
|
||||||
|
const l = local.replace(/^v/, '').split('.').map(Number);
|
||||||
|
for (let i = 0; i < Math.max(r.length, l.length); i++) {
|
||||||
|
if ((r[i] || 0) > (l[i] || 0)) return true;
|
||||||
|
if ((r[i] || 0) < (l[i] || 0)) return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitHub API headers (with optional PAT for higher rate limits)
|
||||||
|
function githubHeaders() {
|
||||||
|
const h = { 'Accept': 'application/vnd.github.v3+json' };
|
||||||
|
if (GITHUB_PAT && GITHUB_PAT !== '{{GITHUB_PAT}}') {
|
||||||
|
h['Authorization'] = `token ${GITHUB_PAT}`;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch versions from GitHub (releases or tags)
|
||||||
|
async function fetchVersions(devMode) {
|
||||||
|
const endpoint = devMode
|
||||||
|
? `${GITHUB_API}/tags?per_page=20`
|
||||||
|
: `${GITHUB_API}/releases?per_page=20`;
|
||||||
|
|
||||||
|
const resp = await fetch(endpoint, { headers: githubHeaders() });
|
||||||
|
if (!resp.ok) throw new Error(`GitHub API ${resp.status}`);
|
||||||
|
const data = await resp.json();
|
||||||
|
|
||||||
|
// Normalize to common format: { tag, name, url, body }
|
||||||
|
return data.map(item => {
|
||||||
|
const tag = devMode ? item.name : item.tag_name;
|
||||||
|
return {
|
||||||
|
tag,
|
||||||
|
name: devMode ? tag : (item.name || tag),
|
||||||
|
url: `${INSTALLER_BASE}/${tag}/dist/install.html`,
|
||||||
|
body: item.body || ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periodic update check (runs on init + every hour)
|
||||||
async function checkForUpdates() {
|
async function checkForUpdates() {
|
||||||
console.log('[Kantine] Starting update check...');
|
|
||||||
const currentVersion = '{{VERSION}}';
|
const currentVersion = '{{VERSION}}';
|
||||||
const versionUrl = 'https://raw.githubusercontent.com/TauNeutrino/kantine-overview/main/version.txt';
|
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
|
||||||
const installerUrl = 'https://htmlpreview.github.io/?https://github.com/TauNeutrino/kantine-overview/blob/main/dist/install.html';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[Kantine] Fetching ${versionUrl}...`);
|
const versions = await fetchVersions(devMode);
|
||||||
const resp = await fetch(versionUrl, { cache: 'no-cache' });
|
if (!versions.length) return;
|
||||||
console.log(`[Kantine] Fetch status: ${resp.status}`);
|
|
||||||
|
|
||||||
if (!resp.ok) {
|
// Cache for version menu
|
||||||
console.warn(`[Kantine] Version Check HTTP Error: ${resp.status}`);
|
localStorage.setItem('kantine_version_cache', JSON.stringify({
|
||||||
return;
|
timestamp: Date.now(), devMode, versions
|
||||||
}
|
}));
|
||||||
const remoteVersion = (await resp.text()).trim();
|
|
||||||
|
|
||||||
console.log(`[Kantine] Version Check: Local [${currentVersion}] vs Remote [${remoteVersion}]`);
|
const latest = versions[0].tag;
|
||||||
|
console.log(`[Kantine] Version Check: Local [${currentVersion}] vs Latest [${latest}] (${devMode ? 'dev' : 'stable'})`);
|
||||||
|
|
||||||
// Check if remote is NEWER (simple semver check)
|
if (!isNewer(latest, currentVersion)) return;
|
||||||
const isNewer = (remote, local) => {
|
|
||||||
if (!remote || !local) return false;
|
|
||||||
const r = remote.replace(/^v/, '').split('.').map(Number);
|
|
||||||
const l = local.replace(/^v/, '').split('.').map(Number);
|
|
||||||
for (let i = 0; i < Math.max(r.length, l.length); i++) {
|
|
||||||
if ((r[i] || 0) > (l[i] || 0)) return true;
|
|
||||||
if ((r[i] || 0) < (l[i] || 0)) return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isNewer(remoteVersion, currentVersion)) {
|
console.log(`[Kantine] Update verfügbar: ${latest}`);
|
||||||
console.log('[Kantine] No update needed (Remote is not newer).');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[Kantine] Update verfügbar: ${remoteVersion}`);
|
|
||||||
|
|
||||||
// Show 🆕 icon in header (only once)
|
// Show 🆕 icon in header (only once)
|
||||||
const headerTitle = document.querySelector('.header-left h1');
|
const headerTitle = document.querySelector('.header-left h1');
|
||||||
if (headerTitle && !headerTitle.querySelector('.update-icon')) {
|
if (headerTitle && !headerTitle.querySelector('.update-icon')) {
|
||||||
const icon = document.createElement('a');
|
const icon = document.createElement('a');
|
||||||
icon.className = 'update-icon';
|
icon.className = 'update-icon';
|
||||||
icon.href = installerUrl;
|
icon.href = versions[0].url;
|
||||||
icon.target = '_blank';
|
icon.target = '_blank';
|
||||||
icon.innerHTML = '🆕';
|
icon.innerHTML = '🆕';
|
||||||
icon.title = `Update verfügbar: ${remoteVersion} — Klick zum Installieren`;
|
icon.title = `Update: ${latest} — Klick zum Installieren`;
|
||||||
icon.style.cssText = 'margin-left:8px;font-size:1em;text-decoration:none;cursor:pointer;vertical-align:middle;';
|
icon.style.cssText = 'margin-left:8px;font-size:1em;text-decoration:none;cursor:pointer;vertical-align:middle;';
|
||||||
headerTitle.appendChild(icon);
|
headerTitle.appendChild(icon);
|
||||||
}
|
}
|
||||||
@@ -1467,6 +1548,89 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open Version Menu modal
|
||||||
|
function openVersionMenu() {
|
||||||
|
const modal = document.getElementById('version-modal');
|
||||||
|
const container = document.getElementById('version-list-container');
|
||||||
|
const devToggle = document.getElementById('dev-mode-toggle');
|
||||||
|
const currentVersion = '{{VERSION}}';
|
||||||
|
|
||||||
|
if (!modal) return;
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Set current version display
|
||||||
|
const cur = document.getElementById('version-current');
|
||||||
|
if (cur) cur.textContent = currentVersion;
|
||||||
|
|
||||||
|
// Init dev toggle
|
||||||
|
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
|
||||||
|
devToggle.checked = devMode;
|
||||||
|
|
||||||
|
// Load versions (from cache or fresh)
|
||||||
|
async function loadVersions(forceRefresh) {
|
||||||
|
const dm = devToggle.checked;
|
||||||
|
container.innerHTML = '<p style="color:var(--text-secondary);">Lade Versionen...</p>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
let versions;
|
||||||
|
const cached = JSON.parse(localStorage.getItem('kantine_version_cache') || 'null');
|
||||||
|
if (!forceRefresh && cached && cached.devMode === dm && (Date.now() - cached.timestamp < 3600000)) {
|
||||||
|
versions = cached.versions;
|
||||||
|
} else {
|
||||||
|
versions = await fetchVersions(dm);
|
||||||
|
localStorage.setItem('kantine_version_cache', JSON.stringify({
|
||||||
|
timestamp: Date.now(), devMode: dm, versions
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!versions.length) {
|
||||||
|
container.innerHTML = '<p style="color:var(--text-secondary);">Keine Versionen gefunden.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = '<ul class="version-list"></ul>';
|
||||||
|
const list = container.querySelector('.version-list');
|
||||||
|
|
||||||
|
versions.forEach(v => {
|
||||||
|
const isCurrent = v.tag === currentVersion;
|
||||||
|
const isNew = isNewer(v.tag, currentVersion);
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'version-item' + (isCurrent ? ' current' : '');
|
||||||
|
|
||||||
|
let badge = '';
|
||||||
|
if (isCurrent) badge = '<span class="badge-current">✓ Installiert</span>';
|
||||||
|
else if (isNew) badge = '<span class="badge-new">⬆ Neu!</span>';
|
||||||
|
|
||||||
|
let action = '';
|
||||||
|
if (!isCurrent) {
|
||||||
|
action = `<a href="${v.url}" target="_blank" class="install-link" title="${v.tag} installieren">Installieren</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="version-info">
|
||||||
|
<strong>${v.tag}</strong>
|
||||||
|
${badge}
|
||||||
|
</div>
|
||||||
|
${action}
|
||||||
|
`;
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
container.innerHTML = `<p style="color:#e94560;">Fehler: ${e.message}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadVersions(false);
|
||||||
|
|
||||||
|
// Dev toggle handler
|
||||||
|
devToggle.onchange = () => {
|
||||||
|
localStorage.setItem('kantine_dev_mode', devToggle.checked);
|
||||||
|
// Clear cache to force refresh when mode changes
|
||||||
|
localStorage.removeItem('kantine_version_cache');
|
||||||
|
loadVersions(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// === Order Countdown ===
|
// === Order Countdown ===
|
||||||
function updateCountdown() {
|
function updateCountdown() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
122
style.css
122
style.css
@@ -1412,4 +1412,126 @@ body {
|
|||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Version Menu === */
|
||||||
|
.version-tag {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s ease, text-decoration 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-tag:hover {
|
||||||
|
opacity: 1 !important;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
max-height: 350px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item:hover {
|
||||||
|
background: rgba(100, 116, 139, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-item.current {
|
||||||
|
background: rgba(2, 154, 168, 0.1);
|
||||||
|
border: 1px solid rgba(2, 154, 168, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .version-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .version-item.current {
|
||||||
|
background: rgba(96, 165, 250, 0.12);
|
||||||
|
border: 1px solid rgba(96, 165, 250, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-current {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--success-color);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(5, 150, 105, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-new {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #029aa8;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(2, 154, 168, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .badge-new {
|
||||||
|
color: #60a5fa;
|
||||||
|
background: rgba(96, 165, 250, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-link {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(2, 154, 168, 0.1);
|
||||||
|
color: #029aa8;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid rgba(2, 154, 168, 0.25);
|
||||||
|
transition: all 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-link:hover {
|
||||||
|
background: rgba(2, 154, 168, 0.2);
|
||||||
|
border-color: rgba(2, 154, 168, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .install-link {
|
||||||
|
color: #60a5fa;
|
||||||
|
background: rgba(96, 165, 250, 0.12);
|
||||||
|
border: 1px solid rgba(96, 165, 250, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .install-link:hover {
|
||||||
|
background: rgba(96, 165, 250, 0.2);
|
||||||
|
border-color: rgba(96, 165, 250, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-toggle {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(100, 116, 139, 0.05);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dev-toggle input[type="checkbox"] {
|
||||||
|
accent-color: #029aa8;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .dev-toggle input[type="checkbox"] {
|
||||||
|
accent-color: #60a5fa;
|
||||||
}
|
}
|
||||||
@@ -101,6 +101,23 @@ try {
|
|||||||
console.log("✅ Static Analysis Passed: 'appendChild(icon)' found.");
|
console.log("✅ Static Analysis Passed: 'appendChild(icon)' found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for GitHub Release Management functions
|
||||||
|
const checks = [
|
||||||
|
[/GITHUB_API/, 'GITHUB_API constant'],
|
||||||
|
[/function\s+fetchVersions/, 'fetchVersions function'],
|
||||||
|
[/function\s+isNewer/, 'isNewer function'],
|
||||||
|
[/function\s+openVersionMenu/, 'openVersionMenu function'],
|
||||||
|
[/kantine_dev_mode/, 'dev-mode localStorage key']
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [regex, label] of checks) {
|
||||||
|
if (!regex.test(code)) {
|
||||||
|
console.error(`❌ Static Analysis Failed: '${label}' not found.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("✅ Static Analysis Passed: All GitHub Release Management functions found.");
|
||||||
|
|
||||||
// Check dynamic logic usage
|
// Check dynamic logic usage
|
||||||
// Note: Since we mock fetch to fail for menu data, the app might perform error handling.
|
// Note: Since we mock fetch to fail for menu data, the app might perform error handling.
|
||||||
// We just want to ensure it doesn't CRASH (exit code) and that our specific feature logic ran.
|
// We just want to ensure it doesn't CRASH (exit code) and that our specific feature logic ran.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v1.2.9
|
v1.3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user