Enrich topic pages with verse text

This commit is contained in:
2025-12-01 13:48:47 -05:00
parent 963170efd9
commit 3f01bb2352
5 changed files with 115 additions and 30 deletions
+2 -2
View File
@@ -14,7 +14,7 @@ from ..utils.pdf import render_html_to_pdf, render_html_to_pdf_async, WEASYPRINT
from ..kjv import bible
from ..cross_references import get_cross_references
from ..reading_plans import get_plan, get_plan_summary
from ..topics import get_all_topics, get_topic
from ..topics import get_all_topics, get_topic_with_text
from ..interlinear_loader import get_interlinear_data, has_interlinear_data
from ..utils.books import normalize_book_name, OT_BOOKS
from ..utils.search import perform_full_text_search
@@ -1230,7 +1230,7 @@ async def api_get_topics():
@router.get("/topics/{topic_name}")
async def api_get_topic(topic_name: str = Path(..., description="Topic name", example="faith")):
"""Get details about a specific topic."""
topic = get_topic(topic_name)
topic = get_topic_with_text(topic_name)
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
+3 -3
View File
@@ -21,7 +21,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
from .kjv import bible, VerseReference
from .cross_references import get_cross_references
from .reading_plans import get_plan, get_all_plans, get_plan_summary
from .topics import get_all_topics, get_topic, search_topics
from .topics import get_all_topics, get_topic_with_text, search_topics
from .interlinear_loader import get_interlinear_data, has_interlinear_data, get_all_interlinear_verses, preload_data, find_verses_by_strongs, count_strongs_occurrences
from .strongs import format_strongs_entry, search_strongs, get_all_strongs
from .books import get_book_data, has_book_data
@@ -1880,7 +1880,7 @@ async def resources_page(request: Request):
async def topic_detail(request: Request, topic_name: str):
"""View verses for a specific topic"""
books = bible.get_books()
topic = get_topic(topic_name)
topic = get_topic_with_text(topic_name)
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
@@ -1914,7 +1914,7 @@ async def topic_detail_pdf(topic_name: str):
detail="PDF generation is not available. WeasyPrint system libraries are not installed."
)
topic = get_topic(topic_name)
topic = get_topic_with_text(topic_name)
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
+31 -19
View File
@@ -153,33 +153,45 @@
{% for verse in subtopic_data.verses %}
<li class="verse-item">
<span class="verse-ref">
{% set ref_parts = verse.ref.rsplit(' ', 1) %}
{% if ref_parts|length == 2 %}
{% set book_name = ref_parts[0] %}
{% set chapter_verse = ref_parts[1] %}
{% if ':' in chapter_verse %}
{% set ch = chapter_verse.split(':')[0] %}
{% set v = chapter_verse.split(':')[1] %}
{% if '-' not in v %}
{# Single verse - make it a link #}
<a href="/book/{{ book_name }}/chapter/{{ ch }}/verse/{{ v }}">{{ verse.ref }}</a>
{% set reference = verse.reference if verse.reference is defined else (verse.ref if verse.ref is defined else verse) %}
{% if reference is not string %}
{% set reference = reference|string %}
{% endif %}
{% set reference = reference.strip() %}
{% if reference %}
{% set ref_parts = reference.rsplit(' ', 1) %}
{% if ref_parts|length == 2 %}
{% set book_name = ref_parts[0] %}
{% set chapter_verse = ref_parts[1] %}
{% if ':' in chapter_verse %}
{% set ch = chapter_verse.split(':')[0] %}
{% set v = chapter_verse.split(':')[1] %}
{% if '-' not in v %}
{# Single verse - make it a link #}
<a href="/book/{{ book_name }}/chapter/{{ ch }}/verse/{{ v }}">{{ reference }}</a>
{% else %}
{# Verse range - link to chapter with anchor #}
{% set v_start = v.split('-')[0] %}
{% set v_end = v.split('-')[1] %}
<a href="/book/{{ book_name }}/chapter/{{ ch }}#verse-{{ v_start }}-{{ v_end }}">{{ reference }}</a>
{% endif %}
{% else %}
{# Verse range - link to chapter with anchor #}
{% set v_start = v.split('-')[0] %}
{% set v_end = v.split('-')[1] %}
<a href="/book/{{ book_name }}/chapter/{{ ch }}#verse-{{ v_start }}-{{ v_end }}">{{ verse.ref }}</a>
{# No colon, just display text #}
{{ reference }}
{% endif %}
{% else %}
{# No colon, just display text #}
{{ verse.ref }}
{{ reference }}
{% endif %}
{% else %}
{{ verse.ref }}
{% endif %}
</span>
{% if verse.note %}
{% if verse.note is defined and verse.note %}
<span class="verse-note">— {{ verse.note }}</span>
{% endif %}
{% if verse.text is defined and verse.text %}
<div style="margin-top: 0.4rem; color: var(--text-secondary); line-height: 1.6;">
{{ verse.text }}
</div>
{% endif %}
</li>
{% endfor %}
</ul>
+11 -2
View File
@@ -97,8 +97,17 @@
<ul class="verse-list">
{% for verse in subtopic.verses %}
<li class="verse-item">
<span class="verse-ref">{{ verse.ref }}</span>
{% if verse.note %}<span class="verse-note">— {{ verse.note }}</span>{% endif %}
{% set reference = verse.reference if verse.reference is defined else (verse.ref if verse.ref is defined else verse) %}
{% if reference is not string %}
{% set reference = reference|string %}
{% endif %}
<span class="verse-ref">{{ reference }}</span>
{% if verse.note is defined and verse.note %}<span class="verse-note">— {{ verse.note }}</span>{% endif %}
{% if verse.text is defined and verse.text %}
<div style="margin-top: 6px; color: #555;">
{{ verse.text }}
</div>
{% endif %}
</li>
{% endfor %}
</ul>
+68 -4
View File
@@ -4,9 +4,12 @@ Organized by major theological and practical topics.
"""
import json
from functools import lru_cache
from pathlib import Path
from typing import Dict, Any
@lru_cache(maxsize=1)
def _load_topics():
"""Load topics from per-topic JSON files, fallback to legacy single file."""
base_dir = Path(__file__).parent / "data"
@@ -27,17 +30,78 @@ def _load_topics():
return aggregated
TOPICS = _load_topics()
def _get_verse_text_for_reference(reference: str) -> str:
"""Look up a single verse text from a reference like 'John 3:16'."""
if not reference or not isinstance(reference, str):
return ""
parts = reference.rsplit(" ", 1)
if len(parts) != 2 or ":" not in parts[1]:
return ""
book = parts[0]
chapter_part, verse_part = parts[1].split(":", 1)
# Use the first verse in a range (e.g., 3:16-17 -> 3:16)
verse_segment = verse_part.split("-")[0]
try:
from .kjv import bible
return bible.get_verse_text(book, int(chapter_part), int(verse_segment)) or ""
except Exception:
return ""
def _enrich_topic_with_text(topic: Dict[str, Any]) -> Dict[str, Any]:
"""Attach verse text (and normalized reference fields) to a topic's verses."""
subtopics = topic.get("subtopics", {})
enriched_subtopics = {}
for subtopic_name, subtopic_data in subtopics.items():
verses_with_text = []
for entry in subtopic_data.get("verses", []):
if isinstance(entry, dict):
reference = entry.get("reference") or entry.get("ref") or ""
note = entry.get("note", "")
else:
reference = str(entry)
note = ""
verse_text = _get_verse_text_for_reference(reference)
verses_with_text.append({
"reference": reference,
"ref": reference,
"text": verse_text,
"note": note
})
enriched_subtopics[subtopic_name] = {
**subtopic_data,
"verses": verses_with_text
}
return {
**topic,
"subtopics": enriched_subtopics
}
def get_all_topics():
"""Get all topics"""
return TOPICS
return _load_topics()
def get_topic(topic_name: str):
"""Get a specific topic"""
return TOPICS.get(topic_name)
return _load_topics().get(topic_name)
@lru_cache(maxsize=None)
def get_topic_with_text(topic_name: str):
"""Get a topic with verse text attached to each reference."""
topic = _load_topics().get(topic_name)
if not topic:
return None
return _enrich_topic_with_text(topic)
def search_topics(query: str):
@@ -45,7 +109,7 @@ def search_topics(query: str):
query_lower = query.lower()
results = []
for topic_name, topic_data in TOPICS.items():
for topic_name, topic_data in _load_topics().items():
if query_lower in topic_name.lower() or query_lower in topic_data.get("description", "").lower():
results.append({
"name": topic_name,