This commit is contained in:
parent
c453295ab0
commit
2ba4daa742
11
CHANGELOG.md
11
CHANGELOG.md
@ -9,6 +9,14 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
|
||||
---
|
||||
|
||||
## [1.52.0] — 2026-09-22
|
||||
|
||||
### Changed
|
||||
- **Magazine: the article-thumbnail grid is gone; "Thumbnails" now means the page-by-page view.** An article is a poor thumbnail unit — it can span many pages or share one with others — so the toggle is back to two states: List (articles, for the title/author/duration/status detail that only makes sense per article) and the page-complete thumbnail grid added earlier. "All pages" is also now the default view on first open.
|
||||
- **Magazine: the page grid's column count is adjustable, 1-12**, via a slider next to the view toggle (shown only in that view) — a "zoom" between an overview of the whole issue and a close look at a few pages, remembered per browser.
|
||||
|
||||
---
|
||||
|
||||
## [1.51.0] — 2026-09-22
|
||||
|
||||
### Fixed
|
||||
@ -4401,7 +4409,8 @@ Initial feature-complete release.
|
||||
|
||||
---
|
||||
|
||||
[Unreleased]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.51.0...HEAD
|
||||
[Unreleased]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.52.0...HEAD
|
||||
[1.52.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.51.0...v1.52.0
|
||||
[1.51.0]: https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/compare/v1.50.0...v1.51.0
|
||||
[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
|
||||
|
||||
@ -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.51.0">
|
||||
<meta name="app-version" content="1.52.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.51.0">
|
||||
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.51.0">
|
||||
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.51.0">
|
||||
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.51.0">
|
||||
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.51.0">
|
||||
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.51.0">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.52.0">
|
||||
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.52.0">
|
||||
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.52.0">
|
||||
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.52.0">
|
||||
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.52.0">
|
||||
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.52.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.51.0"></script>
|
||||
<script src="/static/loader.js?v=1.52.0"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -22,9 +22,10 @@ const state = {
|
||||
sentences: [], // [{ text, blockId, page, bbox }]
|
||||
images: [], // current article's figures, in reading order
|
||||
view: 'library', // library | contents | review | read
|
||||
contentsView: 'list', // list | grid — how the article list itself is shown
|
||||
contentsView: 'pages', // list | pages — how the article list itself is shown
|
||||
contentsSelectMode: false, // batch-select articles for bulk approve/reject
|
||||
contentsSelected: new Set(),
|
||||
pagesCols: 8, // "All pages" grid column count, user-adjustable 1-12
|
||||
playlistCurrent: null, // article id of the MP3 playlist's current track
|
||||
pane: 'reflow', // reflow | pdf
|
||||
playing: false,
|
||||
@ -724,17 +725,21 @@ function renderContents() {
|
||||
bySection.get(key).push(a);
|
||||
});
|
||||
|
||||
const grid = state.contentsView === 'grid';
|
||||
const toggleBtn = $('mag-contents-view-toggle');
|
||||
if (toggleBtn) {
|
||||
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];
|
||||
const nextLabel = { list: 'Thumbnails', pages: 'List' }[state.contentsView];
|
||||
const nextIcon = { list: 'mdi-view-grid-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] +
|
||||
toggleBtn.title = 'Currently: ' + { list: 'article list', pages: 'page thumbnails' }[state.contentsView] +
|
||||
'. Click to switch to ' + nextLabel.toLowerCase() + '.';
|
||||
}
|
||||
const colsControl = $('mag-cols-control');
|
||||
if (colsControl) colsControl.hidden = state.contentsView !== 'pages';
|
||||
if (state.contentsView === 'pages') {
|
||||
const range = $('mag-cols-range'), valueEl = $('mag-cols-value');
|
||||
if (range) range.value = state.pagesCols;
|
||||
if (valueEl) valueEl.textContent = String(state.pagesCols);
|
||||
renderPagesView(host);
|
||||
renderContentsBulkBar();
|
||||
magUpdateAudioZipLink();
|
||||
@ -768,53 +773,37 @@ function renderContents() {
|
||||
|
||||
let html = '';
|
||||
bySection.forEach((arts, section) => {
|
||||
html += '<div class="mag-section"><h4>' + esc(section) + '</h4>' + (grid ? '<div class="mag-grid">' : '<ul class="mag-toc">');
|
||||
html += '<div class="mag-section"><h4>' + esc(section) + '</h4><ul class="mag-toc">';
|
||||
arts.forEach((a) => {
|
||||
const conf = confidenceLabel(a.confidence);
|
||||
const done = a.status === 'approved';
|
||||
const progress = (state.doc.progress || {})[a.id];
|
||||
const statusBadge = '<span class="mag-pill ' + (done ? 'ok' : '') + '">' +
|
||||
(done ? 'approved' : a.status === 'rejected' ? 'rejected' : 'needs review') + '</span>';
|
||||
if (grid) {
|
||||
const firstPage = (a.pages || [])[0];
|
||||
html += '<div class="mag-card-wrap"><div class="mag-card' + (done ? ' is-approved' : '') + '">' +
|
||||
selectHtml(a) +
|
||||
(firstPage
|
||||
? '<img class="mag-card-thumb" loading="lazy" alt="Page ' + firstPage + '" ' +
|
||||
'src="/api/magazine/' + esc(state.doc.id) + '/pages/' + firstPage + '">'
|
||||
: '<div class="mag-card-thumb"></div>') +
|
||||
titleHtml(a, 'mag-card-title') +
|
||||
(a.author ? '<span class="mag-card-sub">by ' + esc(a.author) + '</span>' : '') +
|
||||
'<span class="mag-card-sub">' + fmtPages(a.pages) + ' · ' + fmtDuration(a.estimated_seconds) + '</span>' +
|
||||
'<div class="mag-toc-meta"><span class="mag-pill ' + conf.cls + '" title="' + esc(conf.text) + '">' +
|
||||
esc(conf.badge) + '</span>' + statusBadge + '</div>' +
|
||||
'<div class="mag-toc-actions">' + actionsHtml(a, done, progress) + '</div>' +
|
||||
'</div></div>';
|
||||
} else {
|
||||
html += '<li class="mag-toc-row' + (done ? ' is-approved' : '') + '">' +
|
||||
selectHtml(a) +
|
||||
'<div class="mag-toc-main">' +
|
||||
titleHtml(a, 'mag-toc-title') +
|
||||
(a.author ? '<span class="mag-toc-by">by ' + esc(a.author) + '</span>' : '') +
|
||||
'</div>' +
|
||||
'<div class="mag-toc-meta">' +
|
||||
'<span>' + fmtPages(a.pages) + '</span>' +
|
||||
'<span>' + fmtDuration(a.estimated_seconds) + '</span>' +
|
||||
'<span class="mag-pill ' + conf.cls + '" title="' + esc(conf.text) + '">' +
|
||||
esc(conf.badge) + '</span>' +
|
||||
statusBadge +
|
||||
'</div>' +
|
||||
'<div class="mag-toc-actions">' + actionsHtml(a, done, progress) + '</div></li>';
|
||||
}
|
||||
html += '<li class="mag-toc-row' + (done ? ' is-approved' : '') + '">' +
|
||||
selectHtml(a) +
|
||||
'<div class="mag-toc-main">' +
|
||||
titleHtml(a, 'mag-toc-title') +
|
||||
(a.author ? '<span class="mag-toc-by">by ' + esc(a.author) + '</span>' : '') +
|
||||
'</div>' +
|
||||
'<div class="mag-toc-meta">' +
|
||||
'<span>' + fmtPages(a.pages) + '</span>' +
|
||||
'<span>' + fmtDuration(a.estimated_seconds) + '</span>' +
|
||||
'<span class="mag-pill ' + conf.cls + '" title="' + esc(conf.text) + '">' +
|
||||
esc(conf.badge) + '</span>' +
|
||||
statusBadge +
|
||||
'</div>' +
|
||||
'<div class="mag-toc-actions">' + actionsHtml(a, done, progress) + '</div></li>';
|
||||
});
|
||||
html += grid ? '</div></div>' : '</ul></div>';
|
||||
html += '</ul></div>';
|
||||
});
|
||||
host.innerHTML = html;
|
||||
renderContentsBulkBar();
|
||||
magUpdateAudioZipLink();
|
||||
}
|
||||
|
||||
const MAG_CONTENTS_VIEW_CYCLE = ['list', 'grid', 'pages'];
|
||||
const MAG_CONTENTS_VIEW_CYCLE = ['list', 'pages'];
|
||||
const MAG_PAGES_COLS_KEY = 'ttsvc_mag_pages_cols';
|
||||
|
||||
// Every page, 1..pageCount, regardless of whether an article claims it --
|
||||
// the article grid/list above can only ever show what detection already
|
||||
@ -830,7 +819,7 @@ function renderPagesView(host) {
|
||||
if (!byPage.has(p)) byPage.set(p, []);
|
||||
byPage.get(p).push(a);
|
||||
}));
|
||||
let html = '<div class="mag-grid mag-pages-grid">';
|
||||
let html = '<div class="mag-pages-grid" style="--cols:' + state.pagesCols + '">';
|
||||
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') + '">' +
|
||||
@ -851,6 +840,15 @@ function renderPagesView(host) {
|
||||
host.innerHTML = html;
|
||||
}
|
||||
|
||||
function magSetPagesCols(n) {
|
||||
state.pagesCols = Math.max(1, Math.min(12, parseInt(n, 10) || 8));
|
||||
try { localStorage.setItem(MAG_PAGES_COLS_KEY, String(state.pagesCols)); } catch (_) {}
|
||||
const valueEl = $('mag-cols-value');
|
||||
if (valueEl) valueEl.textContent = String(state.pagesCols);
|
||||
const grid = document.querySelector('.mag-pages-grid');
|
||||
if (grid) grid.style.setProperty('--cols', state.pagesCols);
|
||||
}
|
||||
|
||||
function magToggleContentsView() {
|
||||
const i = MAG_CONTENTS_VIEW_CYCLE.indexOf(state.contentsView);
|
||||
state.contentsView = MAG_CONTENTS_VIEW_CYCLE[(i + 1) % MAG_CONTENTS_VIEW_CYCLE.length];
|
||||
@ -3559,6 +3557,8 @@ async function magInit() {
|
||||
try {
|
||||
const savedView = localStorage.getItem(MAG_CONTENTS_VIEW_KEY);
|
||||
if (MAG_CONTENTS_VIEW_CYCLE.includes(savedView)) state.contentsView = savedView;
|
||||
const savedCols = parseInt(localStorage.getItem(MAG_PAGES_COLS_KEY), 10);
|
||||
if (savedCols >= 1 && savedCols <= 12) state.pagesCols = savedCols;
|
||||
} catch (_) {}
|
||||
if (!$('mag-library-list')) return; // section not mounted
|
||||
magLoadLibrary();
|
||||
@ -3594,6 +3594,7 @@ root.magContentsCommitTitle = magContentsCommitTitle;
|
||||
root.magGenerateAllAudio = magGenerateAllAudio;
|
||||
root.magTogglePlaylist = magTogglePlaylist;
|
||||
root.magReviewPage = magReviewPage;
|
||||
root.magSetPagesCols = magSetPagesCols;
|
||||
root.magPlaylistPlay = magPlaylistPlay;
|
||||
root.magContentsToggleArticle = magContentsToggleArticle;
|
||||
root.magContentsSelectAllVisible = magContentsSelectAllVisible;
|
||||
|
||||
@ -58,9 +58,14 @@
|
||||
</button>
|
||||
<h3 id="mag-doc-title">Magazine</h3>
|
||||
<div class="mag-bar-actions">
|
||||
<button type="button" class="btn-sm" id="mag-contents-view-toggle" onclick="magToggleContentsView()" title="Switch between a list and a thumbnail grid of articles">
|
||||
<button type="button" class="btn-sm" id="mag-contents-view-toggle" onclick="magToggleContentsView()" title="Switch between an article list and a page-by-page thumbnail grid">
|
||||
<span class="mdi mdi-view-grid-outline"></span> Thumbnails
|
||||
</button>
|
||||
<span class="mag-cols-control" id="mag-cols-control" hidden>
|
||||
<label for="mag-cols-range">Columns</label>
|
||||
<input type="range" id="mag-cols-range" min="1" max="12" step="1" value="8" oninput="magSetPagesCols(this.value)">
|
||||
<span id="mag-cols-value" class="muted">8</span>
|
||||
</span>
|
||||
<button type="button" class="btn-sm" id="mag-contents-select-toggle" aria-pressed="false" onclick="magContentsToggleSelect()" title="Select several articles to approve or reject at once">
|
||||
<span class="mdi mdi-checkbox-multiple-marked-outline"></span> Select
|
||||
</button>
|
||||
|
||||
@ -4935,8 +4935,13 @@ 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 { display: grid; grid-template-columns: repeat(var(--cols, 8), 1fr); gap: 12px; }
|
||||
.mag-pages-grid .mag-card-thumb { height: 120px; }
|
||||
.mag-cols-control { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--subtext); }
|
||||
.mag-cols-control input[type="range"] { width: 90px; accent-color: var(--accent); }
|
||||
#mag-cols-value { min-width: 16px; text-align: right; }
|
||||
@media (max-width: 900px) { .mag-pages-grid { grid-template-columns: repeat(min(var(--cols, 8), 4), 1fr); } }
|
||||
@media (max-width: 560px) { .mag-pages-grid { grid-template-columns: repeat(min(var(--cols, 8), 2), 1fr); } }
|
||||
.mag-card.is-uncovered { border-color: var(--yellow, #d97706);
|
||||
background: color-mix(in srgb, var(--yellow, #d97706) 6%, var(--surface)); }
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user