refactor: wire audiobook namespace APIs

This commit is contained in:
mARTin-B78 2026-09-04 11:54:56 +02:00
parent 6f2c0b7b3d
commit e096b3e529
6 changed files with 43 additions and 9 deletions

View File

@ -8,6 +8,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
## [Unreleased] ## [Unreleased]
### Changed ### Changed
- **Audiobook namespace wiring.** Network helpers and public casting, recast, export, and save actions now register
after their lazy modules load, completing the namespace bridge without removing legacy global entry points.
- **Audiobook live cast-view extraction.** The live cast feed, roster, settings, and prompt controls now live in - **Audiobook live cast-view extraction.** The live cast feed, roster, settings, and prompt controls now live in
`audiobook-cast.js`; the legacy `audiobookCastView` entry point remains available for compatibility. `audiobook-cast.js`; the legacy `audiobookCastView` entry point remains available for compatibility.
- **Rehearser voice-matching extraction.** Local voice matching, preview-line selection, fish.audio search/import, - **Rehearser voice-matching extraction.** Local voice matching, preview-line selection, fish.audio search/import,

View File

@ -1 +1 @@
1.24.48 1.24.49

View File

@ -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.24.48"> <meta name="app-version" content="1.24.49">
<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.24.48"> <link rel="stylesheet" href="/static/style.css?v=1.24.49">
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.48"> <link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.49">
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.24.48"> <link rel="stylesheet" href="/static/css/background-jobs.css?v=1.24.49">
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.48"> <link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.49">
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.48"> <link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.49">
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.48"> <link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.49">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── --> <!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -397,7 +397,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.24.48"></script> <script src="/static/loader.js?v=1.24.49"></script>
</body> </body>
</html> </html>

View File

@ -3562,3 +3562,14 @@ function audiobookExportDialog() {
modal.style.display = 'flex'; modal.style.display = 'flex';
} }
} }
window.TTVC = window.TTVC || {};
window.TTVC.audiobook = window.TTVC.audiobook || {};
Object.assign(window.TTVC.audiobook, {
startCast: audiobookCast,
openCastView: audiobookOpenCastView,
recastUnknown: audiobookRecastUnknown,
recastSelectedCharacters: audiobookRecastSelectedCharacters,
export: audiobookExport,
saveAsRehearsal: audiobookSaveAsRehearsal,
});

View File

@ -85,3 +85,11 @@ async function audiobookAttributeStream(body, view, outerSignal, idleTimeoutMs =
if (outerSignal) outerSignal.removeEventListener('abort', onAbort); if (outerSignal) outerSignal.removeEventListener('abort', onAbort);
} }
} }
window.TTVC = window.TTVC || {};
window.TTVC.audiobook = window.TTVC.audiobook || {};
Object.assign(window.TTVC.audiobook, {
fetchWithTimeout: audiobookFetchWithTimeout,
attributeStream: audiobookAttributeStream,
timeoutSeconds: audiobookTimeoutSeconds,
});

View File

@ -23,3 +23,16 @@ test('heavy feature modules load on demand', async ({ page }) => {
await page.waitForTimeout(100); await page.waitForTimeout(100);
expect(requests.filter(url => url.includes('/rehearser.js')).length).toBe(countBeforeRepeat); expect(requests.filter(url => url.includes('/rehearser.js')).length).toBe(countBeforeRepeat);
}); });
test('Audiobook lazy modules publish their namespace APIs after loading', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
await page.evaluate(() => window.navTo('s-reader'));
await page.waitForFunction(() => typeof window.audiobookCastView === 'function', null, { timeout: 15_000 });
await expect.poll(() => page.evaluate(() => ({
castView: typeof window.TTVC?.audiobook?.cast?.castView,
stream: typeof window.TTVC?.audiobook?.attributeStream,
startCast: typeof window.TTVC?.audiobook?.startCast,
}))).toEqual({ castView: 'function', stream: 'function', startCast: 'function' });
});