Add Books + editable Recommended Resources sections; improve content block links; v1.1.38

- CustomBlock: add linkUrl/linkLabel fields for a styled button below the body
- Body text auto-linkifies raw URLs into gold anchor tags
- New 'books' placement: Reading List grid on Downloads page (cover, desc, Amazon btn)
- New 'recommended' placement: editable Recommended Resources section on Downloads
  page; falls back to Truth For Life widget when empty
- Admin: Books and Recommended Resources panels added to Content nav

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-08-26 08:16:36 -04:00
1 parent a35e04320a
commit eb1d3ddd9c
5 files changed
+348 -5

No files matched your search

+81 -2
View File
@@ -885,6 +885,16 @@ function ShareShowButton() {
)
}
function linkifyText(text: string) {
const urlRegex = /(https?:\/\/[^\s]+)/g
const parts = text.split(urlRegex)
return parts.map((part, i) =>
urlRegex.test(part)
? <a key={i} href={part} target="_blank" rel="noopener noreferrer" className="custom-block-inline-link">{part}</a>
: part
)
}
function CustomBlocksSection({ content, page }: { content: SiteContent; page: string }) {
const blocks = (content.customBlocks ?? []).filter(block => {
const blockPage = block.page ?? 'downloads'
@@ -897,7 +907,12 @@ function CustomBlocksSection({ content, page }: { content: SiteContent; page: st
<section key={block.id} className="section-custom-block" aria-label={block.heading}>
<div className="section-inner custom-block-inner">
<h2>{block.heading}</h2>
<p>{block.body}</p>
<p>{linkifyText(block.body)}</p>
{block.linkUrl && (
<a href={block.linkUrl} target="_blank" rel="noopener noreferrer" className="btn-primary custom-block-link-btn">
{block.linkLabel || 'Learn More'}
</a>
)}
</div>
</section>
))}
@@ -1723,6 +1738,69 @@ function EpisodesPage({ content }: { content: SiteContent }) {
)
}
function RecommendedResourcesSection({ content }: { content: SiteContent }) {
const recommended = (content.customLinks ?? []).filter(link => link.placement === 'recommended')
if (recommended.length === 0) return <TruthForLifeWidget type="resourcead" />
return (
<section className="section-tfl" aria-label="Recommended resources">
<div className="section-inner">
<div className="tfl-label-row">
<h2 className="section-heading" style={{ marginBottom: '1rem' }}>
<span className="ornament">✦</span> Recommended Resources <span className="ornament">✦</span>
</h2>
</div>
<div className="recommended-resources-list">
{recommended.map(item => (
<a key={item.id} href={item.url} target="_blank" rel="noopener noreferrer" className="recommended-resource-card">
{item.imageUrl && (
<img src={item.imageUrl} alt={item.label} className="recommended-resource-image" loading="lazy" />
)}
<div className="recommended-resource-meta">
<span className="recommended-resource-title">{item.label}</span>
{item.description && <p className="recommended-resource-description">{item.description}</p>}
<span className="recommended-resource-action">Learn more →</span>
</div>
</a>
))}
</div>
</div>
</section>
)
}
function BooksSectionSection({ content }: { content: SiteContent }) {
const books = (content.customLinks ?? []).filter(link => link.placement === 'books')
if (books.length === 0) return null
return (
<section className="section-resources section-books" aria-label="Recommended books">
<div className="section-inner">
<div className="download-library-head">
<p className="eyebrow">Reading List</p>
<h2 className="section-heading">Books worth your time.</h2>
</div>
<div className="books-list">
{books.map(book => (
<div key={book.id} className="book-card">
{book.imageUrl && (
<img src={book.imageUrl} alt={book.label} className="book-card-cover" loading="lazy" />
)}
<div className="book-card-meta">
<span className="book-card-title">{book.label}</span>
{book.description && <p className="book-card-description">{book.description}</p>}
{book.amazonUrl && (
<a href={book.amazonUrl} target="_blank" rel="noopener noreferrer" className="btn-primary book-card-btn">
{book.amazonLabel || 'View on Amazon'}
</a>
)}
</div>
</div>
))}
</div>
</div>
</section>
)
}
function ResourcesPage({ content }: { content: SiteContent }) {
const siteTitle = content.seo?.title || DEFAULTS.seo.title
usePageMeta(
@@ -1735,8 +1813,9 @@ function ResourcesPage({ content }: { content: SiteContent }) {
<SiteSearchBar content={content} />
<StudyGuideSection content={content} />
<DownloadLibrarySection content={content} />
<BooksSectionSection content={content} />
<CustomBlocksSection content={content} page="downloads" />
<TruthForLifeWidget type="resourcead" />
<RecommendedResourcesSection content={content} />
<SiteFooter content={content} />
<AnalyticsConsentBanner content={content} />
</div>