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

This commit is contained in:
mARTin-B78 2026-09-22 23:18:37 +02:00
parent ab10516383
commit 5b0fc1cb65
5 changed files with 74 additions and 16 deletions

View File

@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
---
## [1.50.0] — 2026-09-22
### Added
- **Magazine: an "All pages" view.** Articles are a poor unit for "have I seen everything" — one can span many pages, several can share one page, and neither the list nor thumbnail view (both article-grained) can show either case. This adds a third view, cycled by the same toggle button, showing every page 1..N with its own thumbnail and whichever article(s) claim it (a page shared by two pieces shows both; a page claimed by none is flagged and links straight into Review there via the page-coverage jump added earlier). Articles remain the unit for narration, Approve, and audio export, which are inherently article-scoped — this is a complete-picture view alongside them, not a replacement.
---
## [1.49.0] — 2026-09-22
### Added
@ -4386,7 +4393,8 @@ Initial feature-complete release.
---
[Unreleased]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.49.0...HEAD
[Unreleased]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.50.0...HEAD
[1.50.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.49.0...v1.50.0
[1.49.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.48.2...v1.49.0
[1.48.2]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.48.1...v1.48.2
[1.48.1]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.48.0...v1.48.1

View File

@ -1 +1 @@
1.49.0
1.50.0

View File

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

View File

@ -695,10 +695,18 @@ function renderContents() {
const grid = state.contentsView === 'grid';
const toggleBtn = $('mag-contents-view-toggle');
if (toggleBtn) {
toggleBtn.innerHTML = grid
? '<span class="mdi mdi-view-list-outline"></span> List'
: '<span class="mdi mdi-view-grid-outline"></span> Thumbnails';
toggleBtn.setAttribute('aria-pressed', String(grid));
const nextLabel = { list: 'Thumbnails', grid: 'All pages', pages: 'List' }[state.contentsView];
const nextIcon = { list: 'mdi-view-grid-outline', grid: 'mdi-view-module-outline', pages: 'mdi-view-list-outline' }[state.contentsView];
toggleBtn.innerHTML = '<span class="mdi ' + nextIcon + '"></span> ' + nextLabel;
toggleBtn.setAttribute('aria-pressed', String(state.contentsView !== 'list'));
toggleBtn.title = 'Currently: ' + { list: 'list', grid: 'thumbnails (by article)', pages: 'all pages' }[state.contentsView] +
'. Click to switch to ' + nextLabel.toLowerCase() + '.';
}
if (state.contentsView === 'pages') {
renderPagesView(host);
renderContentsBulkBar();
magUpdateAudioZipLink();
return;
}
const actionsHtml = (a, done, progress) =>
@ -774,8 +782,46 @@ function renderContents() {
magUpdateAudioZipLink();
}
const MAG_CONTENTS_VIEW_CYCLE = ['list', 'grid', 'pages'];
// Every page, 1..pageCount, regardless of whether an article claims it --
// the article grid/list above can only ever show what detection already
// grouped, which is exactly what left 36% of one real issue invisible (see
// magUncoveredPageRanges). This is the complete picture: a page can belong
// to zero articles (never reviewed), one, or several (a page shared by two
// short pieces), and this shows all three cases rather than picking one.
function renderPagesView(host) {
const pageCount = state.doc?.pageCount || 0;
if (!pageCount) { host.innerHTML = '<p class="muted">Page count unknown yet.</p>'; return; }
const byPage = new Map();
state.articles.forEach((a) => (a.pages || []).forEach((p) => {
if (!byPage.has(p)) byPage.set(p, []);
byPage.get(p).push(a);
}));
let html = '<div class="mag-grid mag-pages-grid">';
for (let p = 1; p <= pageCount; p++) {
const arts = byPage.get(p) || [];
html += '<div class="mag-card-wrap"><div class="mag-card' + (arts.length ? '' : ' is-uncovered') + '">' +
'<img class="mag-card-thumb" loading="lazy" alt="Page ' + p + '" src="/api/magazine/' + esc(state.doc.id) + '/pages/' + p + '">' +
'<span class="mag-card-title">Page ' + p + '</span>' +
(arts.length
? '<span class="mag-card-sub">' + esc(arts[0].title || 'Untitled') +
(arts.length > 1 ? ' <span class="muted">+' + (arts.length - 1) + ' more</span>' : '') + '</span>' +
'<div class="mag-toc-actions">' +
arts.map((a) => '<button type="button" class="btn-sm" onclick="magReview(\'' + a.id + '\')" title="' +
esc(a.title || 'Untitled') + '">' + (arts.length > 1 ? esc((a.title || '?').slice(0, 14)) : 'Review') + '</button>').join('') +
'</div>'
: '<span class="mag-pill">no article</span>' +
'<div class="mag-toc-actions"><button type="button" class="btn-sm" onclick="magReviewPage(' + p + ')">Review this page</button></div>') +
'</div></div>';
}
html += '</div>';
host.innerHTML = html;
}
function magToggleContentsView() {
state.contentsView = state.contentsView === 'grid' ? 'list' : 'grid';
const i = MAG_CONTENTS_VIEW_CYCLE.indexOf(state.contentsView);
state.contentsView = MAG_CONTENTS_VIEW_CYCLE[(i + 1) % MAG_CONTENTS_VIEW_CYCLE.length];
try { localStorage.setItem(MAG_CONTENTS_VIEW_KEY, state.contentsView); } catch (_) {}
renderContents();
}
@ -3471,7 +3517,7 @@ async function magInit() {
}
try {
const savedView = localStorage.getItem(MAG_CONTENTS_VIEW_KEY);
if (savedView === 'grid' || savedView === 'list') state.contentsView = savedView;
if (MAG_CONTENTS_VIEW_CYCLE.includes(savedView)) state.contentsView = savedView;
} catch (_) {}
if (!$('mag-library-list')) return; // section not mounted
magLoadLibrary();

View File

@ -4935,6 +4935,10 @@ input[list="lang-codes"] {
background: color-mix(in srgb, var(--yellow, #d97706) 10%, var(--surface)); }
.mag-coverage-banner .mdi { color: var(--yellow, #d97706); flex-shrink: 0; margin-top: 2px; }
.mag-coverage-ranges { display: flex; flex-wrap: wrap; gap: 6px; }
.mag-pages-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
.mag-pages-grid .mag-card-thumb { height: 120px; }
.mag-card.is-uncovered { border-color: var(--yellow, #d97706);
background: color-mix(in srgb, var(--yellow, #d97706) 6%, var(--surface)); }
.mag-audio-export { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
padding: 10px 14px; margin-bottom: 12px; border: 1px solid var(--border); border-radius: var(--radius);