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:
1 parent
a35e04320a
commit
eb1d3ddd9c
5 files changed
+348
-5
No files matched your search
+81
-2
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user