Release v1.47.1
Some checks are pending
Test and SonarQube / quality (push) Waiting to run

This commit is contained in:
mARTin-B78 2026-09-22 22:56:06 +02:00
parent e00103dfb9
commit 0fa042f494
6 changed files with 41 additions and 10 deletions

View File

@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
--- ---
## [1.47.1] — 2026-09-22
### Added
- **Magazine: a progress bar for "Generate all audio."** The export status was a single line of text that jumped between "Article N/Total…" and a mid-render "sentence X/Y" — now a `<progress>` bar plus a plain "N / Total" count track the same numbers visually, parsed from the job's own progress text.
---
## [1.47.0] — 2026-09-22 ## [1.47.0] — 2026-09-22
### Added ### Added
@ -4351,7 +4358,8 @@ Initial feature-complete release.
--- ---
[Unreleased]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.47.0...HEAD [Unreleased]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.47.1...HEAD
[1.47.1]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.47.0...v1.47.1
[1.47.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.46.0...v1.47.0 [1.47.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.46.0...v1.47.0
[1.46.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.45.0...v1.46.0 [1.46.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.45.0...v1.46.0
[1.45.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.44.0...v1.45.0 [1.45.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.44.0...v1.45.0

View File

@ -1 +1 @@
1.47.0 1.47.1

View File

@ -10,7 +10,7 @@
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<meta name="color-scheme" content="light dark"> <meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#2563EB"> <meta name="theme-color" content="#2563EB">
<meta name="app-version" content="1.47.0"> <meta name="app-version" content="1.47.1">
<link rel="manifest" href="/manifest.webmanifest"> <link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml"> <link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/icon.svg"> <link rel="apple-touch-icon" href="/static/icon.svg">
@ -27,12 +27,12 @@
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── --> <!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css"> <link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/style.css?v=1.47.0"> <link rel="stylesheet" href="/static/style.css?v=1.47.1">
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.47.0"> <link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.47.1">
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.47.0"> <link rel="stylesheet" href="/static/css/background-jobs.css?v=1.47.1">
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.47.0"> <link rel="stylesheet" href="/static/css/voice-design.css?v=1.47.1">
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.47.0"> <link rel="stylesheet" href="/static/css/rehearser.css?v=1.47.1">
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.47.0"> <link rel="stylesheet" href="/static/css/audiobook.css?v=1.47.1">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── --> <!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -403,7 +403,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script> </script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton --> <!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.47.0"></script> <script src="/static/loader.js?v=1.47.1"></script>
</body> </body>
</html> </html>

View File

@ -822,7 +822,11 @@ async function magGenerateAllAudio() {
if (!voice) return say('Pick a narrator voice for the export first', 'err'); if (!voice) return say('Pick a narrator voice for the export first', 'err');
const btn = $('mag-audio-export-btn'); const btn = $('mag-audio-export-btn');
const status = $('mag-audio-export-status'); const status = $('mag-audio-export-status');
const progressWrap = $('mag-audio-export-progress-wrap');
const progressBar = $('mag-audio-export-progress');
const progressCount = $('mag-audio-export-count');
if (btn) btn.disabled = true; if (btn) btn.disabled = true;
if (progressWrap) progressWrap.hidden = false;
let jobId; let jobId;
try { try {
const r = await api('/api/magazine/' + state.doc.id + '/audio/batch', { const r = await api('/api/magazine/' + state.doc.id + '/audio/batch', {
@ -832,6 +836,7 @@ async function magGenerateAllAudio() {
jobId = r.job_id; jobId = r.job_id;
} catch (e) { } catch (e) {
if (btn) btn.disabled = false; if (btn) btn.disabled = false;
if (progressWrap) progressWrap.hidden = true;
return say('Could not start audio export: ' + e.message, 'err'); return say('Could not start audio export: ' + e.message, 'err');
} }
for (;;) { for (;;) {
@ -839,6 +844,15 @@ async function magGenerateAllAudio() {
try { job = await api('/api/jobs/' + jobId); } try { job = await api('/api/jobs/' + jobId); }
catch (e) { if (btn) btn.disabled = false; return say('Could not check audio export: ' + e.message, 'err'); } catch (e) { if (btn) btn.disabled = false; return say('Could not check audio export: ' + e.message, 'err'); }
if (status) status.textContent = job.detail || 'Working…'; if (status) status.textContent = job.detail || 'Working…';
// The job's own detail text always leads with "N/Total" -- either
// "Article N/Total…" between articles, or "(N/Total) Title: sentence
// X/Y" while one is actively rendering -- so one simple match covers
// both without the backend needing a separate numeric progress field.
const m = /(\d+)\/(\d+)/.exec(job.detail || '');
if (m && progressBar && progressCount) {
progressBar.max = m[2]; progressBar.value = m[1];
progressCount.textContent = m[1] + ' / ' + m[2];
}
if (job.done) { if (job.done) {
say(job.error ? 'Audio export failed: ' + job.error : (job.detail || 'Audio export done'), job.error ? 'err' : 'ok'); say(job.error ? 'Audio export failed: ' + job.error : (job.detail || 'Audio export done'), job.error ? 'err' : 'ok');
break; break;
@ -847,6 +861,7 @@ async function magGenerateAllAudio() {
} }
if (btn) btn.disabled = false; if (btn) btn.disabled = false;
if (status) status.textContent = ''; if (status) status.textContent = '';
if (progressWrap) progressWrap.hidden = true;
await magRefreshArticles(); await magRefreshArticles();
renderContents(); renderContents();
if (!$('mag-playlist')?.hidden) renderPlaylistTracks(); if (!$('mag-playlist')?.hidden) renderPlaylistTracks();

View File

@ -90,6 +90,10 @@
<a class="btn-sm" id="mag-audio-zip-link" href="#" hidden download> <a class="btn-sm" id="mag-audio-zip-link" href="#" hidden download>
<span class="mdi mdi-folder-zip-outline"></span> Download all (ZIP) <span class="mdi mdi-folder-zip-outline"></span> Download all (ZIP)
</a> </a>
<span class="mag-audio-export-progress-wrap" hidden id="mag-audio-export-progress-wrap">
<progress id="mag-audio-export-progress" max="1" value="0"></progress>
<span id="mag-audio-export-count" class="muted"></span>
</span>
<span id="mag-audio-export-status" class="muted" role="status" aria-live="polite"></span> <span id="mag-audio-export-status" class="muted" role="status" aria-live="polite"></span>
</div> </div>
<div id="mag-playlist" class="mag-playlist" hidden> <div id="mag-playlist" class="mag-playlist" hidden>

View File

@ -4963,6 +4963,10 @@ input[list="lang-codes"] {
} }
.mag-audio-export select { padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; .mag-audio-export select { padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px;
background: var(--surface); color: var(--text); } background: var(--surface); color: var(--text); }
.mag-audio-export-progress-wrap { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 120px; }
.mag-audio-export-progress-wrap progress { flex: 1; min-width: 80px; max-width: 220px; height: 8px;
accent-color: var(--accent); }
#mag-audio-export-count { flex-shrink: 0; font-variant-numeric: tabular-nums; }
/* Clicking a title edits it in place -- a dashed underline on hover/focus is /* Clicking a title edits it in place -- a dashed underline on hover/focus is
the only cue it's editable, since a permanent border on every card title the only cue it's editable, since a permanent border on every card title