Scripture Index: verse of the week (admin override or weekly rotation through covered passages); v2.45.0
Publish Container / build (push) Successful in 17m33s
Publish Container / build (push) Successful in 17m33s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e9d9dd0d50
commit
a1adfa0ef1
6 files changed
+75
-4
No files matched your search
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "siteforge",
|
||||
"version": "2.44.0",
|
||||
"version": "2.45.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "siteforge",
|
||||
"version": "2.44.0",
|
||||
"version": "2.45.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1135.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "siteforge",
|
||||
"private": true,
|
||||
"version": "2.44.0",
|
||||
"version": "2.45.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -884,6 +884,7 @@ const ADMIN_SECTION_LINKS: Partial<Record<AdminView, AdminSectionLink[]>> = {
|
||||
{ id: 'global-header', label: 'Header' },
|
||||
{ id: 'global-platforms', label: 'Platform URLs' },
|
||||
{ id: 'global-banner', label: 'Analytics Banner' },
|
||||
{ id: 'global-verse', label: 'Verse of the Week' },
|
||||
{ id: 'global-welcome-email', label: 'Welcome Email' },
|
||||
],
|
||||
'start-here': [
|
||||
@@ -1027,6 +1028,7 @@ const FIELDS: Array<{ key: StringField; label: string; multiline?: boolean; sect
|
||||
{ key: 'platformCreatorUrl', label: 'Platform — Creator Profile URL', section: 'global' },
|
||||
{ key: 'platformKofiUrl', label: 'Platform — Ko-fi URL', section: 'global' },
|
||||
{ key: 'platformKofiQrUrl', label: 'Platform — Ko-fi QR Code Image URL', section: 'global' },
|
||||
{ key: 'verseOfTheWeek', label: 'Scripture Index — Verse of the Week (optional)', section: 'global' },
|
||||
{ key: 'welcomeEmailSubject', label: 'Welcome Email — Subject', section: 'global' },
|
||||
{ key: 'welcomeEmailGreetingPrefix', label: 'Welcome Email — Greeting Prefix', section: 'global' },
|
||||
{ key: 'welcomeEmailIntro', label: 'Welcome Email — Intro Paragraph', multiline: true, section: 'global' },
|
||||
@@ -6436,6 +6438,19 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
|
||||
</div>
|
||||
))}
|
||||
|
||||
</>}
|
||||
{tabOf('global', 'global-footer') === 'global-verse' && <>
|
||||
<div className="admin-panel-subhead" id="global-verse"><h3>Verse of the Week</h3></div>
|
||||
<div className="admin-field">
|
||||
<label htmlFor="field-verseOfTheWeek">Verse of the Week (optional)</label>
|
||||
<input id="field-verseOfTheWeek" type="text" placeholder="Titus 2:11-12" value={(form.verseOfTheWeek as string) ?? ''} onChange={e => handleChange('verseOfTheWeek', e.target.value)} />
|
||||
<p className="admin-field-hint">Shown at the top of the Scripture Index. Leave blank and the site picks a different covered passage each week automatically. Use a full reference with the book name.</p>
|
||||
{(() => {
|
||||
const vowProblem = checkPassageReference(String(form.verseOfTheWeek ?? ''), '')
|
||||
return vowProblem ? <p className="admin-field-warning" role="alert" style={{ color: '#e0a030', fontSize: '0.85rem', margin: '0.35rem 0 0' }}>⚠ {vowProblem}</p> : null
|
||||
})()}
|
||||
</div>
|
||||
|
||||
</>}
|
||||
{tabOf('global', 'global-footer') === 'global-welcome-email' && <>
|
||||
<div className="admin-panel-subhead" id="global-welcome-email"><h3>Welcome Email</h3></div>
|
||||
|
||||
+5
-1
@@ -13718,6 +13718,10 @@ a.study-track-card:hover {
|
||||
.si-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
||||
.si-verse-btn { margin-left: auto; font: inherit; font-size: 0.8rem; background: none; border: 0; padding: 0; cursor: pointer; color: var(--brand-muted); text-decoration: underline; }
|
||||
.si-verse-btn:hover { color: var(--brand-gold); }
|
||||
.si-vow { margin-bottom: 1rem; padding: 1.1rem 1.25rem; border: 1px solid var(--brand-gold-dark); border-radius: 12px; background: radial-gradient(ellipse at top left, rgba(201, 168, 76, 0.12), transparent 70%), var(--brand-black-3); }
|
||||
.si-vow-eyebrow { margin: 0 0 0.25rem; font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--brand-gold); }
|
||||
.si-vow-ref { margin: 0 0 0.5rem; font-family: var(--brand-font-heading); font-size: 1.6rem; font-weight: 600; line-height: 1.15; }
|
||||
.si-vow-text { margin: 0 0 0.75rem; font-style: italic; line-height: 1.65; color: var(--brand-muted); }
|
||||
.si-top { margin-bottom: 1rem; padding: 0.9rem 1rem; background: var(--brand-black-3); border: 1px solid var(--brand-border); border-radius: 12px; }
|
||||
.si-top-title { margin: 0 0 0.6rem; font-family: var(--brand-font-heading); font-size: 1.15rem; font-weight: 600; }
|
||||
.si-top-list { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
||||
@@ -13731,7 +13735,7 @@ a.study-track-card:hover {
|
||||
.si-main { padding: 0; }
|
||||
.si-main, .si-group, .si-top { background: #fff !important; color: #000 !important; }
|
||||
.si-print-title { display: block; font-size: 1.6rem; margin: 0 0 1rem; color: #000; }
|
||||
.si-toolbar, .si-books, .si-meta, .si-top, .si-verse-btn, .si-verse, .si-chev { display: none !important; }
|
||||
.si-toolbar, .si-books, .si-meta, .si-top, .si-vow, .si-verse-btn, .si-verse, .si-chev { display: none !important; }
|
||||
.si-group { border: 0; border-bottom: 1px solid #999; break-inside: avoid-page; border-radius: 0; }
|
||||
.si-group-head { padding: 0.5rem 0; color: #000; }
|
||||
.si-ref { color: #000; }
|
||||
|
||||
+50
@@ -2838,6 +2838,38 @@ function ScriptureIndexPage({ content }: { content: SiteContent }) {
|
||||
return promise
|
||||
}
|
||||
|
||||
// Verse of the week: the admin's pick if set, otherwise rotate through covered passages by calendar week
|
||||
const [weekIndex] = useState(() => Math.floor(Date.now() / (7 * 24 * 60 * 60 * 1000)))
|
||||
const verseOfWeek = useMemo(() => {
|
||||
const override = content.verseOfTheWeek?.trim()
|
||||
const parsed = override ? parseSIRef(override) : null
|
||||
if (parsed) {
|
||||
const key = `${parsed.bookId}-${parsed.chapter}-${parsed.verseStart}-${parsed.endChapter}-${parsed.verseEnd}`
|
||||
const existing = bookGroups.flatMap(g => g.rows).find(r => r.key === key)
|
||||
return existing ?? {
|
||||
key,
|
||||
displayRef: `${SI_BOOK_NAME[parsed.bookId]} ${parsed.chapter}:${parsed.verseStart}${parsed.verseEnd !== parsed.verseStart ? `-${parsed.verseEnd}` : ''}`,
|
||||
bookId: parsed.bookId, chapter: parsed.chapter, endChapter: parsed.endChapter,
|
||||
verseStart: parsed.verseStart, verseEnd: parsed.verseEnd, sources: [],
|
||||
} as ScriptureRow
|
||||
}
|
||||
const pool = bookGroups.flatMap(g => g.rows)
|
||||
if (pool.length === 0) return null
|
||||
return pool[weekIndex % pool.length]
|
||||
}, [bookGroups, content.verseOfTheWeek, weekIndex])
|
||||
const [votText, setVotText] = useState<string | null>(null)
|
||||
const votKey = verseOfWeek?.key
|
||||
useEffect(() => {
|
||||
if (!verseOfWeek || !verseOfWeek.bookId) return
|
||||
let cancelled = false
|
||||
const verseEnd = verseOfWeek.endChapter !== verseOfWeek.chapter ? Number.MAX_SAFE_INTEGER : verseOfWeek.verseEnd
|
||||
fetchChapter(verseOfWeek.bookId, verseOfWeek.chapter)
|
||||
.then(ch => { if (!cancelled) setVotText(extractVerseRange(ch, verseOfWeek.verseStart, verseEnd) || null) })
|
||||
.catch(() => { if (!cancelled) setVotText(null) })
|
||||
return () => { cancelled = true }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [votKey])
|
||||
|
||||
function toggleVerse(row: ScriptureRow) {
|
||||
const open = shownVerses.has(row.key)
|
||||
setShownVerses(prev => {
|
||||
@@ -2921,6 +2953,24 @@ function ScriptureIndexPage({ content }: { content: SiteContent }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!filtering && verseOfWeek && (
|
||||
<div className="si-vow">
|
||||
<p className="si-vow-eyebrow">Verse of the week</p>
|
||||
<h2 className="si-vow-ref">{verseOfWeek.displayRef}</h2>
|
||||
{votText && <p className="si-vow-text">“{votText}”</p>}
|
||||
{verseOfWeek.sources.length > 0 && (
|
||||
<div className="si-sources">
|
||||
{verseOfWeek.sources.slice(0, 3).map((src, i) => (
|
||||
<Link key={i} to={src.url} className={`si-chip si-chip--${src.type}`}>
|
||||
<span className="si-chip-kind">{src.type === 'episode' ? 'Episode' : 'Study'}</span>
|
||||
<span className="si-chip-label">{src.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!filtering && topRows.length > 0 && (
|
||||
<div className="si-top" aria-label="Most-studied passages">
|
||||
<h2 className="si-top-title">Most-studied passages</h2>
|
||||
|
||||
@@ -319,6 +319,7 @@ export interface SiteContent {
|
||||
platformCreatorUrl: string
|
||||
platformKofiUrl?: string
|
||||
platformKofiQrUrl?: string
|
||||
verseOfTheWeek?: string
|
||||
// ── Welcome Email ──
|
||||
welcomeEmailSubject: string
|
||||
welcomeEmailGreetingPrefix: string
|
||||
@@ -571,6 +572,7 @@ export const DEFAULTS: SiteContent = {
|
||||
platformCreatorUrl: 'https://creators.spotify.com/pod/profile/nmemmert/',
|
||||
platformKofiUrl: 'https://ko-fi.com/versebyversewithnate',
|
||||
platformKofiQrUrl: '/images/kofi-qr.png',
|
||||
verseOfTheWeek: '',
|
||||
welcomeEmailSubject: 'Welcome to Verse by Verse with Nate',
|
||||
welcomeEmailGreetingPrefix: 'Glad you\'re here',
|
||||
welcomeEmailIntro: 'Thanks for subscribing to Verse by Verse with Nate - a Bible teaching podcast where we slow down, dig into the text, and pull out the nuggets God has for us word by word.',
|
||||
|
||||
Reference in new issue
Block a user