feat: emphasize update changelog sections

Render release-note headings, categories, items, and body lines as safe structured text with larger typography for section labels and compact spacing for details.
This commit is contained in:
Sucukdeluxe
2026-08-21 00:40:08 +02:00
parent aea0b22a4f
commit 6539db5d52
4 changed files with 68 additions and 24 deletions
+31 -19
View File
@@ -7445,26 +7445,40 @@ function setupListeners() {
}
// --- Update UI ---
function _formatUpdateReleaseNotes(value) {
const output = [];
let pendingBlank = false;
for (const rawLine of String(value || '').replace(/\r\n?/g, '\n').split('\n')) {
let line = rawLine.trim();
if (!line) {
if (output.length > 0) pendingBlank = true;
continue;
}
line = line
.replace(/^#{1,6}\s+/, '')
.replace(/^[-*+]\s+/, '• ')
function _cleanUpdateReleaseText(value) {
return String(value || '')
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
if (pendingBlank && output.at(-1) !== '') output.push('');
output.push(line);
pendingBlank = false;
}
function _renderUpdateReleaseNotes(container, value) {
const fragment = document.createDocumentFragment();
let consumed = 0;
for (const rawLine of String(value || '').replace(/\r\n?/g, '\n').split('\n')) {
const line = rawLine.trim();
if (!line) continue;
const heading = line.match(/^(#{1,6})\s+(.+)$/);
const bullet = line.match(/^[-*+]\s+(.+)$/);
const className = heading
? (heading[1].length <= 2 ? 'update-release-heading' : 'update-release-category')
: bullet
? 'update-release-item'
: 'update-release-line';
const source = heading ? heading[2] : bullet ? `${bullet[1]}` : line;
const cleaned = _cleanUpdateReleaseText(source);
const available = 2400 - consumed;
if (available <= 0) break;
const text = cleaned.length > available ? `${cleaned.slice(0, Math.max(0, available - 1))}` : cleaned;
const element = document.createElement('div');
element.className = className;
element.textContent = text;
fragment.appendChild(element);
consumed += text.length;
if (text.endsWith('…')) break;
}
return output.join('\n');
container.replaceChildren(fragment);
return container.childElementCount > 0;
}
function showUpdateBanner(info) {
@@ -7488,9 +7502,7 @@ function showUpdateBanner(info) {
message.hidden = false;
}
if (notes && notesBody) {
const releaseNotes = _formatUpdateReleaseNotes(info.releaseNotes);
notesBody.textContent = releaseNotes.length > 2400 ? `${releaseNotes.slice(0, 2399)}` : releaseNotes;
notes.hidden = !releaseNotes;
notes.hidden = !_renderUpdateReleaseNotes(notesBody, info.releaseNotes);
}
if (installButton) {
installButton.disabled = false;
+31 -1
View File
@@ -2491,7 +2491,37 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
}
.update-release-notes-body {
white-space: pre-wrap;
white-space: normal;
}
.update-release-heading {
margin: 2px 0 11px;
color: var(--text);
font-size: 14px;
font-weight: 750;
line-height: 1.3;
}
.update-release-category {
margin: 15px 0 7px;
color: var(--text);
font-size: 13px;
font-weight: 700;
line-height: 1.35;
}
.update-release-heading + .update-release-category {
margin-top: 0;
}
.update-release-item,
.update-release-line {
margin: 4px 0;
}
.update-release-item {
padding-left: 11px;
text-indent: -9px;
}
.update-progress-track {
+2
View File
@@ -179,5 +179,7 @@ test('header occupies its final geometry before asynchronous initialization', ()
assert.match(html, /class="update-progress-footer"[\s\S]*id="updateProgressDetails"[\s\S]*id="updateProgressSize"[\s\S]*id="updateProgressSpeed"[\s\S]*id="updateProgressEta"[\s\S]*id="updateProgressText"/u);
assert.match(css, /#updateProgressDetails\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*19ch 2ch 11ch 2ch 10ch;[^}]*column-gap:\s*0;[^}]*font-variant-numeric:\s*tabular-nums;/su);
assert.match(css, /\.update-progress-separator\s*\{[^}]*display:\s*grid;[^}]*place-items:\s*center;/su);
assert.match(css, /\.update-release-heading\s*\{[^}]*font-size:\s*14px;[^}]*font-weight:\s*750;/su);
assert.match(css, /\.update-release-category\s*\{[^}]*font-size:\s*13px;[^}]*font-weight:\s*700;/su);
assert.match(css, /#updateProgressDetails\[hidden\]\s*\{[^}]*display:\s*none;/su);
});
+2 -2
View File
@@ -2447,8 +2447,8 @@ setTimeout(async () => {
check('Update dialog offers cancel and install actions', updateDialogActions === 'Abbrechen|Jetzt installieren');
const updateCloseColor = await wc.executeJavaScript('getComputedStyle(document.getElementById("updateCloseBtn")).color');
check('Update dialog exposes a red close action', updateCloseColor === 'rgb(255, 133, 140)');
const updateDialogChangelog = await wc.executeJavaScript('(() => { const title = document.querySelector(".update-release-notes-title"); const body = document.getElementById("updateReleaseNotesBody"); const titleRect = title?.getBoundingClientRect(); const bodyRect = body?.getBoundingClientRect(); return { hidden: document.getElementById("updateReleaseNotes")?.hidden, title: title?.textContent?.trim(), body: body?.textContent, gap: bodyRect && titleRect ? bodyRect.top - titleRect.bottom : null }; })()');
check('Update dialog renders a compact normalized changelog', updateDialogChangelog.hidden === false && updateDialogChangelog.title === 'Changelog' && updateDialogChangelog.body === 'New in this version\\n\\nMenus and navigation\\n\\n• Added live language switching.\\n• Improved settings layout.' && updateDialogChangelog.gap <= 10);
const updateDialogChangelog = await wc.executeJavaScript('(() => { const title = document.querySelector(".update-release-notes-title"); const body = document.getElementById("updateReleaseNotesBody"); const heading = body?.querySelector(".update-release-heading"); const category = body?.querySelector(".update-release-category"); const items = [...(body?.querySelectorAll(".update-release-item") || [])]; const titleRect = title?.getBoundingClientRect(); const bodyRect = body?.getBoundingClientRect(); return { hidden: document.getElementById("updateReleaseNotes")?.hidden, title: title?.textContent?.trim(), heading: heading?.textContent, headingSize: getComputedStyle(heading).fontSize, category: category?.textContent, categorySize: getComputedStyle(category).fontSize, items: items.map(item => item.textContent).join("|"), gap: bodyRect && titleRect ? bodyRect.top - titleRect.bottom : null }; })()');
check('Update dialog renders a compact changelog with larger headings and categories', updateDialogChangelog.hidden === false && updateDialogChangelog.title === 'Changelog' && updateDialogChangelog.heading === 'New in this version' && updateDialogChangelog.headingSize === '14px' && updateDialogChangelog.category === 'Menus and navigation' && updateDialogChangelog.categorySize === '13px' && updateDialogChangelog.items === '• Added live language switching.|• Improved settings layout.' && updateDialogChangelog.gap <= 10);
const updateDialogSize = await wc.executeJavaScript('(() => ({ width: document.querySelector(".update-dialog")?.getBoundingClientRect().width, notesHeight: document.getElementById("updateReleaseNotes")?.getBoundingClientRect().height }))()');
check('Update dialog and changelog provide more horizontal and vertical room', updateDialogSize.width === 576 && updateDialogSize.notesHeight === 264);