From 2eeae4a7c13d93ae18fa7f4b9f11a72108e71eb2 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Wed, 25 Mar 2026 21:34:59 +0300 Subject: [PATCH] feat: add release notes overlay with Markdown rendering - Replace truncated plaintext release notes with full-screen overlay rendered via `marked` library - Server reconnection does a hard page reload instead of custom event --- .gitea/workflows/release.yml | 32 ++++++-- server/package-lock.json | 19 ++++- server/package.json | 3 +- .../src/wled_controller/static/css/modal.css | 73 +++++++++++++++++++ server/src/wled_controller/static/js/app.ts | 3 + .../src/wled_controller/static/js/core/api.ts | 5 +- .../static/js/features/update.ts | 36 ++++++--- .../wled_controller/static/locales/en.json | 1 + .../wled_controller/static/locales/ru.json | 1 + .../wled_controller/static/locales/zh.json | 1 + .../templates/modals/settings.html | 18 +++-- 11 files changed, 167 insertions(+), 25 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 2f4965a..c346dd0 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -12,6 +12,9 @@ jobs: outputs: release_id: ${{ steps.create.outputs.release_id }} steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Create Gitea release id: create env: @@ -30,12 +33,31 @@ jobs: REPO=$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]') DOCKER_IMAGE="${SERVER_HOST}/${REPO}" + # Scan for RELEASE_NOTES.md (check repo root first, then recursively) + NOTES_FILE=$(find . -maxdepth 3 -name "RELEASE_NOTES.md" -type f | head -1) + if [ -n "$NOTES_FILE" ]; then + export RELEASE_NOTES=$(cat "$NOTES_FILE") + echo "Found release notes: $NOTES_FILE" + else + export RELEASE_NOTES="" + echo "No RELEASE_NOTES.md found" + fi + # Build release body via Python to avoid YAML escaping issues BODY_JSON=$(python3 -c " - import json, sys + import json, sys, os, textwrap + tag = '$TAG' image = '$DOCKER_IMAGE' - body = f'''## Downloads + release_notes = os.environ.get('RELEASE_NOTES', '') + + sections = [] + + if release_notes.strip(): + sections.append(release_notes.strip()) + + sections.append(textwrap.dedent(f''' + ## Downloads | Platform | File | Description | |----------|------|-------------| @@ -58,9 +80,9 @@ jobs: 1. Change the default API key in config/default_config.yaml 2. Open http://localhost:8080 and discover your WLED devices 3. See INSTALLATION.md for detailed configuration - ''' - import textwrap - print(json.dumps(textwrap.dedent(body).strip())) + ''').strip()) + + print(json.dumps('\n\n'.join(sections))) ") RELEASE=$(curl -s -X POST "$BASE_URL/releases" \ diff --git a/server/package-lock.json b/server/package-lock.json index 262410d..1ee47a9 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "chart.js": "^4.5.1", - "elkjs": "^0.11.1" + "elkjs": "^0.11.1", + "marked": "^17.0.5" }, "devDependencies": { "esbuild": "^0.27.4", @@ -495,6 +496,17 @@ "@esbuild/win32-x64": "0.27.4" } }, + "node_modules/marked": { + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.5.tgz", + "integrity": "sha512-6hLvc0/JEbRjRgzI6wnT2P1XuM1/RrrDEX0kPt0N7jGm1133g6X7DlxFasUIx+72aKAr904GTxhSLDrd5DIlZg==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -744,6 +756,11 @@ "@esbuild/win32-x64": "0.27.4" } }, + "marked": { + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.5.tgz", + "integrity": "sha512-6hLvc0/JEbRjRgzI6wnT2P1XuM1/RrrDEX0kPt0N7jGm1133g6X7DlxFasUIx+72aKAr904GTxhSLDrd5DIlZg==" + }, "typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", diff --git a/server/package.json b/server/package.json index 2ee5942..0207458 100644 --- a/server/package.json +++ b/server/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "chart.js": "^4.5.1", - "elkjs": "^0.11.1" + "elkjs": "^0.11.1", + "marked": "^17.0.5" } } diff --git a/server/src/wled_controller/static/css/modal.css b/server/src/wled_controller/static/css/modal.css index a51f4df..a80bd70 100644 --- a/server/src/wled_controller/static/css/modal.css +++ b/server/src/wled_controller/static/css/modal.css @@ -448,6 +448,79 @@ min-height: 0; } +/* ── Release notes content ─────────────────────────────────── */ + +.release-notes-content { + flex: 1; + overflow-y: auto; + padding: 1rem 1.5rem; + font-size: 0.9rem; + line-height: 1.6; + color: var(--text-color); + background: var(--bg-secondary); + border-radius: 8px; +} + +.release-notes-content h2, +.release-notes-content h3, +.release-notes-content h4 { + margin: 1.2em 0 0.4em; + color: var(--text-color); +} + +.release-notes-content h2 { font-size: 1.2rem; } +.release-notes-content h3 { font-size: 1.05rem; } +.release-notes-content h4 { font-size: 0.95rem; } + +.release-notes-content pre { + background: #0d0d0d; + color: #d4d4d4; + padding: 0.75rem 1rem; + border-radius: 6px; + overflow-x: auto; + font-size: 0.82rem; +} + +.release-notes-content code { + background: var(--bg-tertiary, #2a2a2a); + padding: 0.15em 0.4em; + border-radius: 3px; + font-size: 0.88em; +} + +.release-notes-content pre code { + background: none; + padding: 0; +} + +.release-notes-content a { + color: var(--primary-color); +} + +.release-notes-content hr { + border: none; + border-top: 1px solid var(--border-color); + margin: 1rem 0; +} + +.release-notes-content table { + width: 100%; + border-collapse: collapse; + margin: 0.5rem 0; +} + +.release-notes-content th, +.release-notes-content td { + border: 1px solid var(--border-color); + padding: 0.4rem 0.6rem; + text-align: left; + font-size: 0.85rem; +} + +.release-notes-content th { + background: var(--bg-tertiary, #2a2a2a); +} + /* ── Log viewer base ───────────────────────────────────────── */ .log-viewer-output { diff --git a/server/src/wled_controller/static/js/app.ts b/server/src/wled_controller/static/js/app.ts index 630cdf7..023f5dc 100644 --- a/server/src/wled_controller/static/js/app.ts +++ b/server/src/wled_controller/static/js/app.ts @@ -203,6 +203,7 @@ import { loadUpdateStatus, initUpdateListener, checkForUpdates, loadUpdateSettings, saveUpdateSettings, dismissUpdate, initUpdateSettingsPanel, applyUpdate, + openReleaseNotes, closeReleaseNotes, } from './features/update.ts'; // ─── Register all HTML onclick / onchange / onfocus globals ─── @@ -572,6 +573,8 @@ Object.assign(window, { dismissUpdate, initUpdateSettingsPanel, applyUpdate, + openReleaseNotes, + closeReleaseNotes, // appearance applyStylePreset, diff --git a/server/src/wled_controller/static/js/core/api.ts b/server/src/wled_controller/static/js/core/api.ts index 0a6d469..b875868 100644 --- a/server/src/wled_controller/static/js/core/api.ts +++ b/server/src/wled_controller/static/js/core/api.ts @@ -228,8 +228,9 @@ export async function loadServerInfo() { const wasOffline = _serverOnline === false; _setConnectionState(true); if (wasOffline) { - // Server came back — reload data - window.dispatchEvent(new CustomEvent('server:reconnected')); + // Server came back — hard reload to ensure fresh data + location.reload(); + return; } // Auth mode detection diff --git a/server/src/wled_controller/static/js/features/update.ts b/server/src/wled_controller/static/js/features/update.ts index 3132a37..601bcc5 100644 --- a/server/src/wled_controller/static/js/features/update.ts +++ b/server/src/wled_controller/static/js/features/update.ts @@ -36,6 +36,7 @@ interface UpdateStatus { } let _lastStatus: UpdateStatus | null = null; +let _releaseNotesBody = ''; // ─── Version badge highlight ──────────────────────────────── @@ -383,18 +384,33 @@ function _renderUpdatePanel(status: UpdateStatus): void { progressBar.parentElement!.style.display = show ? '' : 'none'; } - // Release notes preview - const notesEl = document.getElementById('update-release-notes'); - if (notesEl) { + // Release notes button visibility + const notesGroup = document.getElementById('update-release-notes-group'); + if (notesGroup) { if (status.has_update && status.release && status.release.body) { - const truncated = status.release.body.length > 500 - ? status.release.body.slice(0, 500) + '...' - : status.release.body; - notesEl.textContent = truncated; - notesEl.parentElement!.style.display = ''; + _releaseNotesBody = status.release.body; + notesGroup.style.display = ''; } else { - notesEl.textContent = ''; - notesEl.parentElement!.style.display = 'none'; + _releaseNotesBody = ''; + notesGroup.style.display = 'none'; } } } + +// ─── Release Notes Overlay ───────────────────────────────── + +export function openReleaseNotes(): void { + const overlay = document.getElementById('release-notes-overlay'); + const content = document.getElementById('release-notes-content'); + if (overlay && content) { + import('marked').then(({ marked }) => { + content.innerHTML = marked.parse(_releaseNotesBody) as string; + overlay.style.display = 'flex'; + }); + } +} + +export function closeReleaseNotes(): void { + const overlay = document.getElementById('release-notes-overlay'); + if (overlay) overlay.style.display = 'none'; +} diff --git a/server/src/wled_controller/static/locales/en.json b/server/src/wled_controller/static/locales/en.json index 1146b5e..913cdd1 100644 --- a/server/src/wled_controller/static/locales/en.json +++ b/server/src/wled_controller/static/locales/en.json @@ -1931,6 +1931,7 @@ "update.last_check": "Last check", "update.never": "never", "update.release_notes": "Release Notes", + "update.view_release_notes": "View Release Notes", "update.auto_check_label": "Auto-Check Settings", "update.auto_check_hint": "Periodically check for new releases in the background.", "update.enable": "Enable auto-check", diff --git a/server/src/wled_controller/static/locales/ru.json b/server/src/wled_controller/static/locales/ru.json index 89f848c..e91d2f6 100644 --- a/server/src/wled_controller/static/locales/ru.json +++ b/server/src/wled_controller/static/locales/ru.json @@ -1860,6 +1860,7 @@ "update.last_check": "Последняя проверка", "update.never": "никогда", "update.release_notes": "Примечания к релизу", + "update.view_release_notes": "Открыть примечания к релизу", "update.auto_check_label": "Автоматическая проверка", "update.auto_check_hint": "Периодически проверять наличие новых версий в фоновом режиме.", "update.enable": "Включить автопроверку", diff --git a/server/src/wled_controller/static/locales/zh.json b/server/src/wled_controller/static/locales/zh.json index d74eb49..2f08486 100644 --- a/server/src/wled_controller/static/locales/zh.json +++ b/server/src/wled_controller/static/locales/zh.json @@ -1858,6 +1858,7 @@ "update.last_check": "上次检查", "update.never": "从未", "update.release_notes": "发布说明", + "update.view_release_notes": "查看发布说明", "update.auto_check_label": "自动检查设置", "update.auto_check_hint": "在后台定期检查新版本。", "update.enable": "启用自动检查", diff --git a/server/src/wled_controller/templates/modals/settings.html b/server/src/wled_controller/templates/modals/settings.html index 83cace8..7bcca36 100644 --- a/server/src/wled_controller/templates/modals/settings.html +++ b/server/src/wled_controller/templates/modals/settings.html @@ -239,12 +239,9 @@ - -

 
+
+
+