Podcast workflow steps: read script, record, edit, upload; v2.15.1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f181cdb182
commit
28fc822132
4 files changed
+19
-23
No files matched your search
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "siteforge",
|
"name": "siteforge",
|
||||||
"version": "2.15.0",
|
"version": "2.15.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "siteforge",
|
"name": "siteforge",
|
||||||
"version": "2.15.0",
|
"version": "2.15.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.1135.0",
|
"@aws-sdk/client-s3": "^3.1135.0",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "siteforge",
|
"name": "siteforge",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.15.0",
|
"version": "2.15.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import { fetchAllEpisodes } from './episodes.js'
|
|||||||
|
|
||||||
const WORKFLOW_FILE = path.join(DATA_DIR, 'podcast-workflow.json')
|
const WORKFLOW_FILE = path.join(DATA_DIR, 'podcast-workflow.json')
|
||||||
|
|
||||||
// Same steps as the Nuggets workflow.
|
const STEPS = ['readScript', 'record', 'edit', 'upload']
|
||||||
const STEPS = ['listen', 'coldOpen', 'recordColdOpen', 'edit', 'spotify']
|
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
try {
|
try {
|
||||||
@@ -26,7 +25,6 @@ const save = data => writeFile(WORKFLOW_FILE, JSON.stringify(data, null, 2), 'ut
|
|||||||
function emptyState() {
|
function emptyState() {
|
||||||
return {
|
return {
|
||||||
steps: Object.fromEntries(STEPS.map(s => [s, null])),
|
steps: Object.fromEntries(STEPS.map(s => [s, null])),
|
||||||
coldOpenText: '',
|
|
||||||
notes: '',
|
notes: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +131,6 @@ export function register(app) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof body.coldOpenText === 'string') next.coldOpenText = body.coldOpenText.slice(0, 5000)
|
|
||||||
if (typeof body.notes === 'string') next.notes = body.notes.slice(0, 5000)
|
if (typeof body.notes === 'string') next.notes = body.notes.slice(0, 5000)
|
||||||
wf.items[key] = next
|
wf.items[key] = next
|
||||||
await save(wf)
|
await save(wf)
|
||||||
|
|||||||
+15
-16
@@ -1799,6 +1799,16 @@ function NuggetsWorkflowAdmin() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PodcastStepKey = 'readScript' | 'record' | 'edit' | 'upload'
|
||||||
|
interface PodcastWorkflowState { steps: Record<PodcastStepKey, string | null>; notes: string }
|
||||||
|
|
||||||
|
const PODCAST_STEP_DEFS: { key: PodcastStepKey; label: string; hint: string }[] = [
|
||||||
|
{ key: 'readScript', label: 'Read the script', hint: 'Read it through, mark pacing and emphasis.' },
|
||||||
|
{ key: 'record', label: 'Record the episode', hint: 'Capture the final take.' },
|
||||||
|
{ key: 'edit', label: 'Edit the episode', hint: 'Trim, clean up, mix and master.' },
|
||||||
|
{ key: 'upload', label: 'Upload to Spotify', hint: 'Publish with show notes. It moves to Released once it’s in the feed.' },
|
||||||
|
]
|
||||||
|
|
||||||
// ── Podcast Episode Workflow (Lane A / Lane B) ─────────────────────────────
|
// ── Podcast Episode Workflow (Lane A / Lane B) ─────────────────────────────
|
||||||
interface PodcastWorkflowItem {
|
interface PodcastWorkflowItem {
|
||||||
key: string
|
key: string
|
||||||
@@ -1811,7 +1821,7 @@ interface PodcastWorkflowItem {
|
|||||||
spotifyUrl?: string
|
spotifyUrl?: string
|
||||||
hasScript?: boolean
|
hasScript?: boolean
|
||||||
planned?: boolean
|
planned?: boolean
|
||||||
state: WorkflowState
|
state: PodcastWorkflowState
|
||||||
}
|
}
|
||||||
|
|
||||||
function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season?: number }; laneB: { title: string; season?: number } }) {
|
function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season?: number }; laneB: { title: string; season?: number } }) {
|
||||||
@@ -1856,7 +1866,7 @@ function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season
|
|||||||
setError(e instanceof Error ? e.message : 'Save failed.')
|
setError(e instanceof Error ? e.message : 'Save failed.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const patchLocal = (key: string, patch: Partial<WorkflowState>) =>
|
const patchLocal = (key: string, patch: Partial<PodcastWorkflowState>) =>
|
||||||
setItems(prev => prev.map(i => (i.key === key ? { ...i, state: { ...i.state, ...patch } } : i)))
|
setItems(prev => prev.map(i => (i.key === key ? { ...i, state: { ...i.state, ...patch } } : i)))
|
||||||
|
|
||||||
const addPlanned = async () => {
|
const addPlanned = async () => {
|
||||||
@@ -1880,7 +1890,7 @@ function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season
|
|||||||
}
|
}
|
||||||
|
|
||||||
const label = (i: PodcastWorkflowItem) => `${i.episodeNumber ? `Ep ${i.episodeNumber}` : 'Ep'}${i.title ? ` — ${i.title}` : ''}`
|
const label = (i: PodcastWorkflowItem) => `${i.episodeNumber ? `Ep ${i.episodeNumber}` : 'Ep'}${i.title ? ` — ${i.title}` : ''}`
|
||||||
const doneCount = (i: PodcastWorkflowItem) => (i.released ? WORKFLOW_STEP_DEFS.length : WORKFLOW_STEP_DEFS.filter(s => i.state.steps[s.key]).length)
|
const doneCount = (i: PodcastWorkflowItem) => (i.released ? PODCAST_STEP_DEFS.length : PODCAST_STEP_DEFS.filter(s => i.state.steps[s.key]).length)
|
||||||
|
|
||||||
const row = (i: PodcastWorkflowItem) => (
|
const row = (i: PodcastWorkflowItem) => (
|
||||||
<button
|
<button
|
||||||
@@ -1895,7 +1905,7 @@ function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season
|
|||||||
>
|
>
|
||||||
<strong>{label(i)}</strong>
|
<strong>{label(i)}</strong>
|
||||||
<div style={{ fontSize: '0.78rem', opacity: 0.75 }}>
|
<div style={{ fontSize: '0.78rem', opacity: 0.75 }}>
|
||||||
{i.released ? `Released ${i.releaseDate || ''}` : `${doneCount(i)}/${WORKFLOW_STEP_DEFS.length} steps`}
|
{i.released ? `Released ${i.releaseDate || ''}` : `${doneCount(i)}/${PODCAST_STEP_DEFS.length} steps`}
|
||||||
{!i.released && i.hasScript ? ' · script ready' : ''}
|
{!i.released && i.hasScript ? ' · script ready' : ''}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@@ -1961,7 +1971,7 @@ function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<ol style={{ paddingLeft: 0, listStyle: 'none', margin: 0 }}>
|
<ol style={{ paddingLeft: 0, listStyle: 'none', margin: 0 }}>
|
||||||
{WORKFLOW_STEP_DEFS.map((s, idx) => {
|
{PODCAST_STEP_DEFS.map((s, idx) => {
|
||||||
const completedAt = selected.state.steps[s.key]
|
const completedAt = selected.state.steps[s.key]
|
||||||
const checked = selected.released || Boolean(completedAt)
|
const checked = selected.released || Boolean(completedAt)
|
||||||
return (
|
return (
|
||||||
@@ -1981,17 +1991,6 @@ function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{s.key === 'coldOpen' && (
|
|
||||||
<textarea
|
|
||||||
rows={4}
|
|
||||||
placeholder="Cold open script / hook…"
|
|
||||||
value={selected.state.coldOpenText}
|
|
||||||
disabled={selected.released}
|
|
||||||
onChange={e => patchLocal(selected.key, { coldOpenText: e.target.value })}
|
|
||||||
onBlur={e => save(selected.key, { coldOpenText: e.target.value })}
|
|
||||||
style={{ width: '100%', marginTop: 8 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in new issue
Block a user