This commit is contained in:
parent
ab10516383
commit
5b0fc1cb65
10
CHANGELOG.md
10
CHANGELOG.md
@ -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
|
## [1.49.0] — 2026-09-22
|
||||||
|
|
||||||
### Added
|
### 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.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.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
|
[1.48.1]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.48.0...v1.48.1
|
||||||
|
|||||||
@ -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.49.0">
|
<meta name="app-version" content="1.50.0">
|
||||||
<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.49.0">
|
<link rel="stylesheet" href="/static/style.css?v=1.50.0">
|
||||||
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.49.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.49.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.49.0">
|
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.50.0">
|
||||||
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.49.0">
|
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.50.0">
|
||||||
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.49.0">
|
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.50.0">
|
||||||
|
|
||||||
|
|
||||||
<!-- ── 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.49.0"></script>
|
<script src="/static/loader.js?v=1.50.0"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -695,10 +695,18 @@ function renderContents() {
|
|||||||
const grid = state.contentsView === 'grid';
|
const grid = state.contentsView === 'grid';
|
||||||
const toggleBtn = $('mag-contents-view-toggle');
|
const toggleBtn = $('mag-contents-view-toggle');
|
||||||
if (toggleBtn) {
|
if (toggleBtn) {
|
||||||
toggleBtn.innerHTML = grid
|
const nextLabel = { list: 'Thumbnails', grid: 'All pages', pages: 'List' }[state.contentsView];
|
||||||
? '<span class="mdi mdi-view-list-outline"></span> List'
|
const nextIcon = { list: 'mdi-view-grid-outline', grid: 'mdi-view-module-outline', pages: 'mdi-view-list-outline' }[state.contentsView];
|
||||||
: '<span class="mdi mdi-view-grid-outline"></span> Thumbnails';
|
toggleBtn.innerHTML = '<span class="mdi ' + nextIcon + '"></span> ' + nextLabel;
|
||||||
toggleBtn.setAttribute('aria-pressed', String(grid));
|
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) =>
|
const actionsHtml = (a, done, progress) =>
|
||||||
@ -774,8 +782,46 @@ function renderContents() {
|
|||||||
magUpdateAudioZipLink();
|
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() {
|
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 (_) {}
|
try { localStorage.setItem(MAG_CONTENTS_VIEW_KEY, state.contentsView); } catch (_) {}
|
||||||
renderContents();
|
renderContents();
|
||||||
}
|
}
|
||||||
@ -3471,7 +3517,7 @@ async function magInit() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const savedView = localStorage.getItem(MAG_CONTENTS_VIEW_KEY);
|
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 (_) {}
|
} catch (_) {}
|
||||||
if (!$('mag-library-list')) return; // section not mounted
|
if (!$('mag-library-list')) return; // section not mounted
|
||||||
magLoadLibrary();
|
magLoadLibrary();
|
||||||
|
|||||||
@ -4935,6 +4935,10 @@ input[list="lang-codes"] {
|
|||||||
background: color-mix(in srgb, var(--yellow, #d97706) 10%, var(--surface)); }
|
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-banner .mdi { color: var(--yellow, #d97706); flex-shrink: 0; margin-top: 2px; }
|
||||||
.mag-coverage-ranges { display: flex; flex-wrap: wrap; gap: 6px; }
|
.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;
|
.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);
|
padding: 10px 14px; margin-bottom: 12px; border: 1px solid var(--border); border-radius: var(--radius);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user