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
This commit is contained in:
@@ -12,6 +12,9 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
release_id: ${{ steps.create.outputs.release_id }}
|
release_id: ${{ steps.create.outputs.release_id }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create Gitea release
|
- name: Create Gitea release
|
||||||
id: create
|
id: create
|
||||||
env:
|
env:
|
||||||
@@ -30,12 +33,31 @@ jobs:
|
|||||||
REPO=$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')
|
REPO=$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')
|
||||||
DOCKER_IMAGE="${SERVER_HOST}/${REPO}"
|
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
|
# Build release body via Python to avoid YAML escaping issues
|
||||||
BODY_JSON=$(python3 -c "
|
BODY_JSON=$(python3 -c "
|
||||||
import json, sys
|
import json, sys, os, textwrap
|
||||||
|
|
||||||
tag = '$TAG'
|
tag = '$TAG'
|
||||||
image = '$DOCKER_IMAGE'
|
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 |
|
| Platform | File | Description |
|
||||||
|----------|------|-------------|
|
|----------|------|-------------|
|
||||||
@@ -58,9 +80,9 @@ jobs:
|
|||||||
1. Change the default API key in config/default_config.yaml
|
1. Change the default API key in config/default_config.yaml
|
||||||
2. Open http://localhost:8080 and discover your WLED devices
|
2. Open http://localhost:8080 and discover your WLED devices
|
||||||
3. See INSTALLATION.md for detailed configuration
|
3. See INSTALLATION.md for detailed configuration
|
||||||
'''
|
''').strip())
|
||||||
import textwrap
|
|
||||||
print(json.dumps(textwrap.dedent(body).strip()))
|
print(json.dumps('\n\n'.join(sections)))
|
||||||
")
|
")
|
||||||
|
|
||||||
RELEASE=$(curl -s -X POST "$BASE_URL/releases" \
|
RELEASE=$(curl -s -X POST "$BASE_URL/releases" \
|
||||||
|
|||||||
19
server/package-lock.json
generated
19
server/package-lock.json
generated
@@ -10,7 +10,8 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"elkjs": "^0.11.1"
|
"elkjs": "^0.11.1",
|
||||||
|
"marked": "^17.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.27.4",
|
"esbuild": "^0.27.4",
|
||||||
@@ -495,6 +496,17 @@
|
|||||||
"@esbuild/win32-x64": "0.27.4"
|
"@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": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
@@ -744,6 +756,11 @@
|
|||||||
"@esbuild/win32-x64": "0.27.4"
|
"@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": {
|
"typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"elkjs": "^0.11.1"
|
"elkjs": "^0.11.1",
|
||||||
|
"marked": "^17.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,6 +448,79 @@
|
|||||||
min-height: 0;
|
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 base ───────────────────────────────────────── */
|
||||||
|
|
||||||
.log-viewer-output {
|
.log-viewer-output {
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ import {
|
|||||||
loadUpdateStatus, initUpdateListener, checkForUpdates,
|
loadUpdateStatus, initUpdateListener, checkForUpdates,
|
||||||
loadUpdateSettings, saveUpdateSettings, dismissUpdate,
|
loadUpdateSettings, saveUpdateSettings, dismissUpdate,
|
||||||
initUpdateSettingsPanel, applyUpdate,
|
initUpdateSettingsPanel, applyUpdate,
|
||||||
|
openReleaseNotes, closeReleaseNotes,
|
||||||
} from './features/update.ts';
|
} from './features/update.ts';
|
||||||
|
|
||||||
// ─── Register all HTML onclick / onchange / onfocus globals ───
|
// ─── Register all HTML onclick / onchange / onfocus globals ───
|
||||||
@@ -572,6 +573,8 @@ Object.assign(window, {
|
|||||||
dismissUpdate,
|
dismissUpdate,
|
||||||
initUpdateSettingsPanel,
|
initUpdateSettingsPanel,
|
||||||
applyUpdate,
|
applyUpdate,
|
||||||
|
openReleaseNotes,
|
||||||
|
closeReleaseNotes,
|
||||||
|
|
||||||
// appearance
|
// appearance
|
||||||
applyStylePreset,
|
applyStylePreset,
|
||||||
|
|||||||
@@ -228,8 +228,9 @@ export async function loadServerInfo() {
|
|||||||
const wasOffline = _serverOnline === false;
|
const wasOffline = _serverOnline === false;
|
||||||
_setConnectionState(true);
|
_setConnectionState(true);
|
||||||
if (wasOffline) {
|
if (wasOffline) {
|
||||||
// Server came back — reload data
|
// Server came back — hard reload to ensure fresh data
|
||||||
window.dispatchEvent(new CustomEvent('server:reconnected'));
|
location.reload();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth mode detection
|
// Auth mode detection
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ interface UpdateStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _lastStatus: UpdateStatus | null = null;
|
let _lastStatus: UpdateStatus | null = null;
|
||||||
|
let _releaseNotesBody = '';
|
||||||
|
|
||||||
// ─── Version badge highlight ────────────────────────────────
|
// ─── Version badge highlight ────────────────────────────────
|
||||||
|
|
||||||
@@ -383,18 +384,33 @@ function _renderUpdatePanel(status: UpdateStatus): void {
|
|||||||
progressBar.parentElement!.style.display = show ? '' : 'none';
|
progressBar.parentElement!.style.display = show ? '' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release notes preview
|
// Release notes button visibility
|
||||||
const notesEl = document.getElementById('update-release-notes');
|
const notesGroup = document.getElementById('update-release-notes-group');
|
||||||
if (notesEl) {
|
if (notesGroup) {
|
||||||
if (status.has_update && status.release && status.release.body) {
|
if (status.has_update && status.release && status.release.body) {
|
||||||
const truncated = status.release.body.length > 500
|
_releaseNotesBody = status.release.body;
|
||||||
? status.release.body.slice(0, 500) + '...'
|
notesGroup.style.display = '';
|
||||||
: status.release.body;
|
|
||||||
notesEl.textContent = truncated;
|
|
||||||
notesEl.parentElement!.style.display = '';
|
|
||||||
} else {
|
} else {
|
||||||
notesEl.textContent = '';
|
_releaseNotesBody = '';
|
||||||
notesEl.parentElement!.style.display = 'none';
|
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';
|
||||||
|
}
|
||||||
|
|||||||
@@ -1931,6 +1931,7 @@
|
|||||||
"update.last_check": "Last check",
|
"update.last_check": "Last check",
|
||||||
"update.never": "never",
|
"update.never": "never",
|
||||||
"update.release_notes": "Release Notes",
|
"update.release_notes": "Release Notes",
|
||||||
|
"update.view_release_notes": "View Release Notes",
|
||||||
"update.auto_check_label": "Auto-Check Settings",
|
"update.auto_check_label": "Auto-Check Settings",
|
||||||
"update.auto_check_hint": "Periodically check for new releases in the background.",
|
"update.auto_check_hint": "Periodically check for new releases in the background.",
|
||||||
"update.enable": "Enable auto-check",
|
"update.enable": "Enable auto-check",
|
||||||
|
|||||||
@@ -1860,6 +1860,7 @@
|
|||||||
"update.last_check": "Последняя проверка",
|
"update.last_check": "Последняя проверка",
|
||||||
"update.never": "никогда",
|
"update.never": "никогда",
|
||||||
"update.release_notes": "Примечания к релизу",
|
"update.release_notes": "Примечания к релизу",
|
||||||
|
"update.view_release_notes": "Открыть примечания к релизу",
|
||||||
"update.auto_check_label": "Автоматическая проверка",
|
"update.auto_check_label": "Автоматическая проверка",
|
||||||
"update.auto_check_hint": "Периодически проверять наличие новых версий в фоновом режиме.",
|
"update.auto_check_hint": "Периодически проверять наличие новых версий в фоновом режиме.",
|
||||||
"update.enable": "Включить автопроверку",
|
"update.enable": "Включить автопроверку",
|
||||||
|
|||||||
@@ -1858,6 +1858,7 @@
|
|||||||
"update.last_check": "上次检查",
|
"update.last_check": "上次检查",
|
||||||
"update.never": "从未",
|
"update.never": "从未",
|
||||||
"update.release_notes": "发布说明",
|
"update.release_notes": "发布说明",
|
||||||
|
"update.view_release_notes": "查看发布说明",
|
||||||
"update.auto_check_label": "自动检查设置",
|
"update.auto_check_label": "自动检查设置",
|
||||||
"update.auto_check_hint": "在后台定期检查新版本。",
|
"update.auto_check_hint": "在后台定期检查新版本。",
|
||||||
"update.enable": "启用自动检查",
|
"update.enable": "启用自动检查",
|
||||||
|
|||||||
@@ -239,12 +239,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Release notes preview -->
|
<!-- Release notes button -->
|
||||||
<div class="form-group" style="display:none">
|
<div class="form-group" id="update-release-notes-group" style="display:none">
|
||||||
<div class="label-row">
|
<button class="btn btn-secondary" onclick="openReleaseNotes()" style="width:100%" data-i18n="update.view_release_notes">View Release Notes</button>
|
||||||
<label data-i18n="update.release_notes">Release Notes</label>
|
|
||||||
</div>
|
|
||||||
<pre id="update-release-notes" style="max-height:200px;overflow-y:auto;font-size:0.82rem;white-space:pre-wrap;word-break:break-word;padding:0.5rem;background:var(--bg-secondary);border-radius:var(--radius-sm);border:1px solid var(--border-color);"></pre>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
@@ -309,3 +306,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<pre id="log-viewer-output" class="log-viewer-output"></pre>
|
<pre id="log-viewer-output" class="log-viewer-output"></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Release Notes Overlay (full-screen, same pattern as log overlay) -->
|
||||||
|
<div id="release-notes-overlay" class="log-overlay" style="display:none;">
|
||||||
|
<button class="log-overlay-close" onclick="closeReleaseNotes()" title="Close" data-i18n-aria-label="aria.close">✕</button>
|
||||||
|
<div class="log-overlay-toolbar">
|
||||||
|
<h3 data-i18n="update.release_notes">Release Notes</h3>
|
||||||
|
</div>
|
||||||
|
<div id="release-notes-content" class="release-notes-content"></div>
|
||||||
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user