Episode scripts: file by season + episode so numbers can repeat per book; search labels for current series; v2.9.6
Publish Container / build (push) Successful in 16m51s
Publish Container / build (push) Successful in 16m51s
- Scripts keyed "<season>-<episode>" (legacy ones migrate on first start; unseasoned ones keep their plain key) - Season backfill handles episode numbers shared across seasons (title match fallback) - Fix: "Backfill seasons from RSS" was swallowed by the upload route - Global search labels script results with the title of the series playing now (both lanes), not just finished books Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ddedf0f81
commit
752611d121
7 files changed
+114
-41
No files matched your search
+9
-9
@@ -1670,7 +1670,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
const [commentSearch, setCommentSearch] = useState('')
|
||||
|
||||
// Episode scripts state
|
||||
const [scriptsList, setScriptsList] = useState<Array<{ episodeNumber: string; title: string; filename: string; season: string | null; uploadedAt: string | null; wordCount: number }>>([])
|
||||
const [scriptsList, setScriptsList] = useState<Array<{ key: string; episodeNumber: string; title: string; filename: string; season: string | null; uploadedAt: string | null; wordCount: number }>>([])
|
||||
const [scriptsLoading, setScriptsLoading] = useState(false)
|
||||
const [scriptUploadEpisode, setScriptUploadEpisode] = useState('')
|
||||
const [scriptUploadTitle, setScriptUploadTitle] = useState('')
|
||||
@@ -3148,7 +3148,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!res.ok) { setScriptUploadMsg(data.message ?? 'Upload failed.'); return }
|
||||
setScriptUploadMsg(`✓ Episode ${scriptUploadEpisode} uploaded — ${data.wordCount?.toLocaleString()} words indexed.`)
|
||||
setScriptUploadMsg(`✓ Episode ${scriptUploadEpisode}${scriptUploadSeason.trim() ? ` (Season ${scriptUploadSeason.trim()})` : ''} uploaded — ${data.wordCount?.toLocaleString()} words indexed.`)
|
||||
setScriptUploadEpisode('')
|
||||
setScriptUploadTitle('')
|
||||
setScriptUploadSeason('')
|
||||
@@ -3181,12 +3181,12 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteScript(episodeNumber: string) {
|
||||
if (!confirm(`Remove script for Episode ${episodeNumber}?`)) return
|
||||
async function handleDeleteScript(key: string, label: string) {
|
||||
if (!confirm(`Remove script for ${label}?`)) return
|
||||
try {
|
||||
const res = await fetch(`/api/admin-episode-scripts/${encodeURIComponent(episodeNumber)}`, { method: 'DELETE' })
|
||||
const res = await fetch(`/api/admin-episode-scripts/${encodeURIComponent(key)}`, { method: 'DELETE' })
|
||||
if (!res.ok) { alert('Failed to delete script.'); return }
|
||||
setScriptsList(prev => prev.filter(s => s.episodeNumber !== episodeNumber))
|
||||
setScriptsList(prev => prev.filter(s => s.key !== key))
|
||||
} catch { alert('Failed to delete script.') }
|
||||
}
|
||||
|
||||
@@ -5684,7 +5684,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
|
||||
/>
|
||||
</div>
|
||||
<div className="admin-field">
|
||||
<label htmlFor="script-season">Season <span style={{ color: '#5a5440', fontWeight: 400 }}>(optional — matches the podcast season number)</span></label>
|
||||
<label htmlFor="script-season">Season <span style={{ color: '#5a5440', fontWeight: 400 }}>(recommended — scripts are filed by season + episode, so episode numbers can repeat in each book)</span></label>
|
||||
<input
|
||||
id="script-season"
|
||||
type="number"
|
||||
@@ -5749,14 +5749,14 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
|
||||
{scriptsList
|
||||
.filter(script => scriptSeasonFilter === 'all' || script.season === scriptSeasonFilter)
|
||||
.map(script => (
|
||||
<div key={script.episodeNumber} className="admin-script-row">
|
||||
<div key={script.key} className="admin-script-row">
|
||||
<div className="admin-script-row-info">
|
||||
<span className="admin-script-ep">Ep. {script.episodeNumber}</span>
|
||||
{script.season && <span className="admin-script-ep">Season {script.season}</span>}
|
||||
<span className="admin-script-title">{script.title}</span>
|
||||
<span className="admin-script-meta">{script.wordCount.toLocaleString()} words · {script.filename}</span>
|
||||
</div>
|
||||
<button type="button" className="btn-admin-remove" onClick={() => handleDeleteScript(script.episodeNumber)}>Remove</button>
|
||||
<button type="button" className="btn-admin-remove" onClick={() => handleDeleteScript(script.key, `Episode ${script.episodeNumber}${script.season ? ` (Season ${script.season})` : ''}`)}>Remove</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user