diff --git a/server/index.mjs b/server/index.mjs index 9d1804b..ae3c15b 100644 --- a/server/index.mjs +++ b/server/index.mjs @@ -85,30 +85,37 @@ function sendFile(res, filePath, status = 200) { createReadStream(filePath).pipe(res) } -function serveStatic(req, res) { - // Strip query, prevent path traversal, default to index.html (SPA fallback). - const urlPath = decodeURIComponent((req.url || '/').split('?')[0]) +// Resolve a request path to an existing static file inside dist/, or null. +function resolveStaticFile(url) { + const urlPath = decodeURIComponent((url || '/').split('?')[0]) const safe = normalize(urlPath).replace(/^(\.\.[/\\])+/, '') - let filePath = join(DIST, safe) - - if (!filePath.startsWith(DIST)) { - res.statusCode = 403 - return res.end('Forbidden') - } - if (existsSync(filePath) && statSync(filePath).isFile()) { - return sendFile(res, filePath) - } - // SPA fallback - const indexFile = join(DIST, 'index.html') - if (existsSync(indexFile)) return sendFile(res, indexFile) - res.statusCode = 404 - res.end('Not found') + const filePath = join(DIST, safe) + if (!filePath.startsWith(DIST)) return null + if (existsSync(filePath) && statSync(filePath).isFile()) return filePath + return null } +const INDEX_HTML = join(DIST, 'index.html') + const server = http.createServer((req, res) => { const url = req.url || '/' + + // 1) Known ABS paths → forward upstream. if (isProxied(url)) return proxy(req, res) - return serveStatic(req, res) + + // 2) An existing static asset of the SPA → serve it. + const file = resolveStaticFile(url) + if (file) return sendFile(res, file) + + // 3) A browser navigating to a client-side route → serve the SPA shell. + const accept = String(req.headers.accept || '') + if (req.method === 'GET' && accept.includes('text/html') && existsSync(INDEX_HTML)) { + return sendFile(res, INDEX_HTML) + } + + // 4) Anything else (any other API/data request) → forward to ABS, so the real + // server never needs to be exposed on its own domain. + return proxy(req, res) }) server.listen(PORT, HOST, () => { diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 8696930..ed8ce71 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -76,10 +76,21 @@ export function Sidebar() { collapsed ? 'w-[68px]' : 'w-60', )} > -
-
- -
+
+ {!collapsed && ( Shelfless )} @@ -115,14 +126,6 @@ export function Sidebar() {
} collapsed={collapsed} /> -
) diff --git a/src/components/player/PlayerBar.tsx b/src/components/player/PlayerBar.tsx index 21193ce..45e2101 100644 --- a/src/components/player/PlayerBar.tsx +++ b/src/components/player/PlayerBar.tsx @@ -17,7 +17,7 @@ import { cn } from '@/lib/cn' import { formatTime } from '@/lib/format' import { coverUrl } from '@/lib/media' import { usePlayerStore, currentChapter } from '@/store/playerStore' -import { PLAYBACK_SPEEDS } from '@/store/settingsStore' +import { PLAYBACK_SPEEDS, useSettingsStore } from '@/store/settingsStore' import { Dropdown, DropdownItem } from '@/components/ui/Dropdown' import { Spinner } from '@/components/ui/Spinner' import { useAudioEngine } from './useAudioEngine' @@ -37,6 +37,7 @@ export function PlayerBar() { const speed = usePlayerStore((s) => s.speed) const volume = usePlayerStore((s) => s.volume) const chapters = usePlayerStore((s) => s.chapters) + const sidebarCollapsed = useSettingsStore((s) => s.sidebarCollapsed) const toggle = usePlayerStore((s) => s.togglePlay) const skip = usePlayerStore((s) => s.skip) @@ -50,7 +51,10 @@ export function PlayerBar() { return (