Some checks are pending
Test and SonarQube / quality (push) Waiting to run
The preview carries each page's regions (percent of the page, type, reading order) for the reading pass and the labelling pass, drawn over the page image with a legend. A server restart that killed the import left it "running" for ever; the status route now reports it as failed. v1.42.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
208 lines
12 KiB
HTML
208 lines
12 KiB
HTML
<div class="section-head">
|
|
<span class="section-icon"><span class="mdi mdi-newspaper-variant-outline"></span></span>
|
|
<div class="section-title">
|
|
<h2>Magazines</h2>
|
|
<p>Listen to a magazine one article at a time — from its true beginning to its true end, without adverts, sidebars or a neighbouring piece spliced into the middle.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Library ──────────────────────────────────────────────────────── -->
|
|
<div id="mag-view-library">
|
|
<div class="card mag-new">
|
|
<h3>Add a magazine or paper</h3>
|
|
<div class="reader-source-grid">
|
|
<div class="reader-paste-panel">
|
|
<div class="reader-source-method">
|
|
<span class="mdi mdi-file-document-edit-outline"></span>
|
|
<div><strong>Import & Metadata</strong><span>Select a PDF file or configure optional title and issue details.</span></div>
|
|
</div>
|
|
<div class="mag-new-grid" style="margin-top: 4px; margin-bottom: 4px;">
|
|
<label for="mag-new-title">Title <span class="muted">(optional)</span>
|
|
<input id="mag-new-title" type="text" placeholder="Auto-filled from PDF filename if blank" autocomplete="off">
|
|
</label>
|
|
<label for="mag-new-issue">Issue <span class="muted">(optional)</span>
|
|
<input id="mag-new-issue" type="text" placeholder="e.g. September 2026" autocomplete="off">
|
|
</label>
|
|
</div>
|
|
<div class="reader-paste-actions" style="margin-top: auto; padding-top: 6px;">
|
|
<label class="btn-primary btn-sm reader-file-pick" id="mag-browse-btn" title="Choose a PDF file from your computer">
|
|
<span class="mdi mdi-folder-open-outline"></span> Durchsuchen
|
|
<input type="file" id="mag-new-file" accept="application/pdf" style="display:none">
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="reader-dropzone" id="mag-dropzone" style="cursor: pointer;">
|
|
<span class="mdi mdi-cloud-upload-outline reader-dropzone-icon" id="mag-dropzone-icon"></span>
|
|
<strong id="mag-dropzone-title">Drop a PDF document</strong>
|
|
<span class="note" id="mag-dropzone-note">Scanned issues work too — pages without selectable text are read with OCR.</span>
|
|
<div class="mag-dropzone-progress" id="mag-dropzone-progress" hidden>
|
|
<div class="mag-dropzone-progress-bar"></div>
|
|
</div>
|
|
<label class="btn-secondary btn-sm reader-file-pick" id="mag-dropzone-pick-btn" title="Choose a PDF file">
|
|
<span class="mdi mdi-file-upload-outline"></span> Import PDF
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3>Your magazines</h3>
|
|
<div id="mag-library-list" class="mag-grid"></div>
|
|
</div>
|
|
|
|
<!-- ── Contents ─────────────────────────────────────────────────────── -->
|
|
<div id="mag-view-contents" hidden>
|
|
<div class="mag-bar">
|
|
<button type="button" class="btn-sm" onclick="magBackToLibrary()">
|
|
<span class="mdi mdi-arrow-left"></span> All magazines
|
|
</button>
|
|
<h3 id="mag-doc-title">Magazine</h3>
|
|
<div class="mag-bar-actions">
|
|
<button type="button" class="btn-sm primary" onclick="magPlayAll()">
|
|
<span class="mdi mdi-playlist-play"></span> Play all articles
|
|
</button>
|
|
<button type="button" class="btn-sm" onclick="magIngest()">
|
|
<span class="mdi mdi-refresh"></span> Re-run detection
|
|
</button>
|
|
<button type="button" class="btn-sm" onclick="magReocrMagazine()" title="Send every page through the configured OCR engine again, then re-detect articles">
|
|
<span class="mdi mdi-text-recognition"></span> OCR again (whole magazine)
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div id="mag-ingest-status" class="mag-status" hidden></div>
|
|
<!-- Decorative "watch it work" visualisation -- the essential
|
|
progress information (percentage, page count, done/failed) is
|
|
in #mag-ingest-status above, which is what a screen reader user
|
|
needs; this panel adds nothing beyond that but noisy churn. -->
|
|
<div id="mag-ingest-preview" class="mag-ingest-preview" hidden aria-hidden="true">
|
|
<div class="mag-ingest-preview-side">
|
|
<span class="mag-ingest-preview-label">Page <span id="mag-ingest-preview-stage"></span></span>
|
|
<div class="mag-ingest-preview-canvas">
|
|
<img id="mag-ingest-preview-img" alt="Page currently being read">
|
|
<div id="mag-ingest-preview-boxes" class="mag-ingest-preview-boxes"></div>
|
|
</div>
|
|
<div id="mag-ingest-preview-legend" class="mag-ingest-preview-legend"></div>
|
|
</div>
|
|
<div class="mag-ingest-preview-resize" id="mag-ingest-preview-resize" role="separator" aria-orientation="vertical" aria-label="Resize page and recognised text panes" title="Drag to resize page and recognised text panes"></div>
|
|
<div class="mag-ingest-preview-side">
|
|
<span class="mag-ingest-preview-label">Recognised text</span>
|
|
<div id="mag-ingest-preview-text" class="mag-ingest-preview-text"></div>
|
|
</div>
|
|
</div>
|
|
<div id="mag-contents"></div>
|
|
</div>
|
|
|
|
<!-- ── Review (Stage D) ─────────────────────────────────────────────── -->
|
|
<div id="mag-view-review" hidden>
|
|
<div class="mag-bar">
|
|
<button type="button" class="btn-sm" onclick="magBackToContents()">
|
|
<span class="mdi mdi-arrow-left"></span> Contents
|
|
</button>
|
|
<h3>Review article boundaries</h3>
|
|
</div>
|
|
<div id="mag-review"></div>
|
|
</div>
|
|
|
|
<!-- ── Reading ──────────────────────────────────────────────────────── -->
|
|
<div id="mag-view-read" hidden>
|
|
<div class="mag-bar">
|
|
<button type="button" class="btn-sm" onclick="magBackToContents()">
|
|
<span class="mdi mdi-arrow-left"></span> Contents
|
|
</button>
|
|
<div class="mag-read-id">
|
|
<h3 id="mag-read-title">Article</h3>
|
|
<span id="mag-read-by" class="muted"></span>
|
|
<span id="mag-read-pages" class="muted"></span>
|
|
</div>
|
|
<button type="button" class="btn-sm" onclick="magToggleDriving()" title="Large controls for listening without looking at the screen">
|
|
<span class="mdi mdi-car-outline"></span> Driving mode
|
|
</button>
|
|
</div>
|
|
|
|
<div class="mag-controls">
|
|
<div class="mag-voice-pick">
|
|
<label for="mag-voice">Narrator</label>
|
|
<select id="mag-voice"></select>
|
|
</div>
|
|
<button type="button" class="btn primary" id="mag-play" onclick="magPlay()"
|
|
aria-label="Play narration">
|
|
<span class="mdi mdi-play"></span> Listen
|
|
</button>
|
|
<button type="button" class="btn" id="mag-prepare" onclick="magPrepareAudio()"
|
|
title="Synthesise the whole article now, so it plays straight through without waiting sentence by sentence">
|
|
<span class="mdi mdi-waveform"></span> Prepare audio
|
|
</button>
|
|
<span id="mag-prep-status" class="muted" role="status" aria-live="polite"></span>
|
|
<span id="mag-read-progress" class="muted" aria-live="polite"></span>
|
|
<span id="mag-queue-progress" class="muted" aria-live="polite"></span>
|
|
<label class="mag-karaoke-toggle" title="Highlight each word approximately as it is spoken">
|
|
<input type="checkbox" id="mag-karaoke-cb" checked onchange="TTVC.magazine.karaoke = this.checked">
|
|
Word highlighting
|
|
</label>
|
|
<label class="mag-karaoke-toggle" title="Speak each figure's description where it appears, if one has been generated in review. Takes effect next time this article is opened.">
|
|
<input type="checkbox" id="mag-read-descriptions">
|
|
Read figure descriptions
|
|
</label>
|
|
<div class="mag-pane-tabs" role="group" aria-label="Reading view">
|
|
<button type="button" class="btn-sm active" data-mag-pane="reflow"
|
|
aria-pressed="true" onclick="magSetPane('reflow')">Article</button>
|
|
<button type="button" class="btn-sm" data-mag-pane="pdf"
|
|
aria-pressed="false" onclick="magSetPane('pdf')">Original page</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="mag-pane-reflow" class="mag-pane">
|
|
<div id="mag-read-image" class="mag-read-image" hidden></div>
|
|
<div id="mag-article" class="mag-article"></div>
|
|
</div>
|
|
|
|
<div id="mag-pane-pdf" class="mag-pane" hidden>
|
|
<div class="mag-page-wrap">
|
|
<img id="mag-page-img" alt="">
|
|
<div id="mag-page-box" class="mag-page-box" hidden></div>
|
|
</div>
|
|
<p id="mag-page-label" class="muted"></p>
|
|
</div>
|
|
|
|
<!-- Podcast mode -->
|
|
<details class="card mag-podcast">
|
|
<summary><span class="mdi mdi-podcast"></span> Podcast mode — discuss this article</summary>
|
|
<p class="muted">Two or three hosts discuss the article for someone who has not read it. The script is written by the configured LLM from the article text; it will not invent facts, but it is a generated discussion, not the author’s words.</p>
|
|
<div class="mag-hosts">
|
|
<div class="mag-voice-pick"><label for="mag-host-a">Host A</label><select id="mag-host-a"></select></div>
|
|
<div class="mag-voice-pick"><label for="mag-host-b">Host B</label><select id="mag-host-b"></select></div>
|
|
<div class="mag-voice-pick"><label for="mag-host-c">Host C <span class="muted">(optional)</span></label><select id="mag-host-c"></select></div>
|
|
</div>
|
|
<div class="mag-podcast-actions">
|
|
<button type="button" class="btn" id="mag-podcast-gen" onclick="magGeneratePodcast()">Generate script</button>
|
|
<button type="button" class="btn primary" id="mag-podcast-play" onclick="magPlayPodcast()" hidden>Play podcast</button>
|
|
</div>
|
|
<div id="mag-podcast-script"></div>
|
|
</details>
|
|
</div>
|
|
|
|
<!-- ── Driving mode overlay ─────────────────────────────────────────── -->
|
|
<!-- Large controls, minimal text, no interaction required beyond play/
|
|
pause/skip -- reuses whatever is already playing rather than its
|
|
own playback path. -->
|
|
<div id="mag-driving" class="mag-driving" hidden role="dialog" aria-label="Driving mode">
|
|
<button type="button" class="mag-driving-exit" onclick="magToggleDriving()" aria-label="Exit driving mode">
|
|
<span class="mdi mdi-close"></span>
|
|
</button>
|
|
<div class="mag-driving-body">
|
|
<h2 id="mag-driving-title">Article</h2>
|
|
<p id="mag-driving-by" class="muted"></p>
|
|
<div id="mag-driving-queue" class="mag-driving-queue" hidden>
|
|
<span id="mag-queue-progress-2" class="muted"></span>
|
|
</div>
|
|
<div class="mag-driving-controls">
|
|
<button type="button" class="mag-driving-btn" onclick="magQueueNext()" aria-label="Next article" title="Next article (only in Play all)">
|
|
<span class="mdi mdi-skip-next"></span>
|
|
</button>
|
|
<button type="button" class="mag-driving-btn mag-driving-play" id="mag-driving-play" onclick="magPlay()" aria-label="Play">
|
|
<span class="mdi mdi-play"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|