feat: expandable ⋯ dividers in Recast Unknown (v1.12.4)

Click any gap divider to reveal the hidden segments between two Unknown
passages inline — shows speaker + text so context is clear before
making an assignment. Displays line count ("42 lines hidden — click
to expand") so users know what they are opening.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-06-27 19:49:04 +02:00
parent 7a75d1ecf4
commit 87cac1f5f5
4 changed files with 41 additions and 9 deletions

View File

@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
---
## [1.12.4] — 2026-06-27
### Added
- **Expandable context dividers in Recast Unknown**: the `⋯` gaps between scattered Unknown segments now show a count ("42 lines hidden — click to expand") and expand inline on click, revealing all segments between two Unknown passages so the user can see who is speaking before and after to make a better assignment.
---
## [1.12.3] — 2026-06-27
### Added

View File

@ -26,7 +26,7 @@
<!-- ── 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.12.3">
<link rel="stylesheet" href="/static/style.css?v=1.12.4">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -336,7 +336,7 @@
<script src="/static/vendor/wavesurfer-regions.min.js"></script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.12.3"></script>
<script src="/static/loader.js?v=1.12.4"></script>
</body>
</html>

View File

@ -881,14 +881,35 @@ STRIKTE FORMAT- UND TEXTREGELN:
feed.appendChild(frag); trim(); renderRoster();
},
note(text) { const r = document.createElement('div'); r.className = 'ab-cv-note'; r.textContent = text; feed.appendChild(r); trim(); },
// Visual gap (three dots) marking that the passages before/after are not
// contiguous — used by Recast unknown, which only shows scattered segments.
divider() {
// Visual gap marking that the passages before/after are not contiguous.
// prevIdx / nextIdx are indices into _audiobook.segments for the gap boundaries.
// Click expands the divider to show all segments in the gap inline.
divider(prevIdx, nextIdx) {
this.clearProcessing();
const r = document.createElement('div');
r.className = 'ab-cv-divider';
r.textContent = '⋯';
r.title = 'There is more text before and after this passage';
r.className = 'ab-cv-divider ab-cv-divider-expand';
const gapFrom = Math.max(0, (prevIdx >= 0 ? prevIdx + 1 : 0));
const segsAll = _audiobook.segments || [];
const gapTo = Math.min(segsAll.length - 1, (nextIdx >= 0 ? nextIdx - 1 : segsAll.length - 1));
const gapCount = Math.max(0, gapTo - gapFrom + 1);
r.innerHTML = `⋯ <span class="ab-cv-divider-hint">${gapCount > 0 ? gapCount + ' line' + (gapCount !== 1 ? 's' : '') + ' hidden — ' : ''}click to expand</span>`;
r.title = 'Click to reveal the text between these passages';
r.addEventListener('click', () => {
const gap = segsAll.slice(gapFrom, gapTo + 1);
if (!gap.length) { r.remove(); return; }
const frag = document.createDocumentFragment();
for (const s of gap) {
const isNarr = s.type !== 'dialogue' || !s.speaker || s.speaker.toLowerCase() === 'narrator';
const spk = isNarr ? 'Narrator' : s.speaker;
const c = colorFor(spk);
const row = document.createElement('div');
row.className = 'ab-cv-row ab-cv-ctx-row' + (isNarr ? ' is-narr' : '');
row.__seg = s;
row.innerHTML = `<span class="ab-cv-spk" style="color:${c}" title="Click to assign character">${escHtml(spk)}${s.emotion ? ' <span style="text-transform:lowercase;font-weight:normal;opacity:.8">(${escHtml(s.emotion)})</span>' : ''}</span><span class="ab-cv-txt">${highlightText(s.text || '')}</span>`;
frag.appendChild(row);
}
r.replaceWith(frag);
});
feed.appendChild(r); trim();
},
// Page-break marker in the casting feed (from source PDF page boundaries).
@ -1028,7 +1049,7 @@ async function audiobookRecastUnknown(overrideUrl, overrideModel) {
// These Unknown lines come from scattered places in the document. Mark a
// gap with "⋯" whenever this passage isn't directly after the previous one.
if (i !== prevIdx + 1) view.divider();
if (i !== prevIdx + 1) view.divider(prevIdx, i);
prevIdx = i;
const start = Math.max(0, i - 12);

View File

@ -4513,6 +4513,10 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
/* ── Recast unknown: gap between non-contiguous passages ───────────────── */
.ab-cv-divider { text-align: center; color: var(--subtext); font-size: 20px; letter-spacing: .35em; line-height: 1; padding: 10px 0 6px; opacity: .55; user-select: none; }
.ab-cv-divider-expand { cursor: pointer; font-size: 14px; letter-spacing: normal; transition: opacity .15s; }
.ab-cv-divider-expand:hover { opacity: .9; color: var(--accent); }
.ab-cv-divider-hint { font-size: 11px; font-weight: 500; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 2px; vertical-align: middle; }
.ab-cv-ctx-row { opacity: .7; background: color-mix(in srgb, var(--panel) 60%, transparent); border-left: 2px solid var(--border); }
.reader-synth-prog { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }
.reader-synth-track { width: 130px; height: 6px; border-radius: 4px; background: var(--panel); overflow: hidden; }
.reader-synth-fill { height: 100%; width: 0; background: rgba(234,179,8,.9); transition: width .15s; }