diff --git a/package.json b/package.json index 53cb37b..a6b9967 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.8.1", + "version": "2.8.2", "type": "module", "scripts": { "dev": "vite", diff --git a/server/helpers.js b/server/helpers.js index 9245900..d0d704b 100644 --- a/server/helpers.js +++ b/server/helpers.js @@ -268,6 +268,7 @@ function sanitizeColossiansStudySections(value) { chapter: Number.isInteger(item.chapter) && item.chapter >= 1 && item.chapter <= 999 ? item.chapter : 1, reference: typeof item.reference === 'string' ? item.reference.trim().slice(0, 40) : '', title: typeof item.title === 'string' ? item.title.trim().slice(0, 160) : '', + episodeNumber: typeof item.episodeNumber === 'string' || typeof item.episodeNumber === 'number' ? String(item.episodeNumber).trim().slice(0, 20) : '', audioEmbedUrl: sanitizeUrl(item.audioEmbedUrl), passageText: typeof item.passageText === 'string' ? item.passageText.trim().slice(0, 12000) : '', summary: typeof item.summary === 'string' ? item.summary.trim().slice(0, 600) : '', diff --git a/server/study-helpers.js b/server/study-helpers.js index 5465537..d88218c 100644 --- a/server/study-helpers.js +++ b/server/study-helpers.js @@ -312,9 +312,11 @@ export function isSectionReleased(section) { export function redactUnreleasedSection(section) { if (!section || typeof section !== 'object') return section - if (isSectionReleased(section)) return section + // episodeNumber is an admin-only reference and is never sent to the public site + const { episodeNumber: _episodeNumber, ...publicSection } = section + if (isSectionReleased(section)) return publicSection return { - ...section, + ...publicSection, passageText: '', commentary: '', greekNotes: [], diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx index 2032ad6..d2d4253 100644 --- a/src/AdminPage.tsx +++ b/src/AdminPage.tsx @@ -14,22 +14,43 @@ import InboxPanel, { type InboxSubmission } from './InboxPanel' import AssetManager, { type AdminAsset } from './AssetManager' +function toLocalInputValue(iso?: string): string { + if (!iso) return '' + const d = new Date(iso) + if (isNaN(d.getTime())) return '' + const pad = (n: number) => String(n).padStart(2, '0') + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}` +} + +function fromLocalInputValue(val: string): string { + return val ? new Date(val).toISOString() : '' +} + interface SortableLessonRowProps { section: ColossiansStudySection study: StudyProgram onEdit: () => void removeStudySection: (studyId: string, sectionId: string) => void + onReleaseChange: (value: string) => void } -function SortableLessonRow({ section, study, onEdit, removeStudySection }: SortableLessonRowProps) { +function SortableLessonRow({ section, study, onEdit, removeStudySection, onReleaseChange }: SortableLessonRowProps) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: section.id }) const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.4 : 1 } const isLive = section.releasedAt ? new Date(section.releasedAt) <= new Date() : false return (