diff --git a/kjvstudy_org/routes/api.py b/kjvstudy_org/routes/api.py index 9434240..7146318 100644 --- a/kjvstudy_org/routes/api.py +++ b/kjvstudy_org/routes/api.py @@ -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") diff --git a/kjvstudy_org/server.py b/kjvstudy_org/server.py index 3186e33..46362de 100644 --- a/kjvstudy_org/server.py +++ b/kjvstudy_org/server.py @@ -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") diff --git a/kjvstudy_org/templates/topic_detail.html b/kjvstudy_org/templates/topic_detail.html index a067ba4..b098d30 100644 --- a/kjvstudy_org/templates/topic_detail.html +++ b/kjvstudy_org/templates/topic_detail.html @@ -153,33 +153,45 @@ {% for verse in subtopic_data.verses %}
  • - {% 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 #} - {{ verse.ref }} + {% 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 #} + {{ reference }} + {% else %} + {# Verse range - link to chapter with anchor #} + {% set v_start = v.split('-')[0] %} + {% set v_end = v.split('-')[1] %} + {{ reference }} + {% endif %} {% else %} - {# Verse range - link to chapter with anchor #} - {% set v_start = v.split('-')[0] %} - {% set v_end = v.split('-')[1] %} - {{ verse.ref }} + {# No colon, just display text #} + {{ reference }} {% endif %} {% else %} - {# No colon, just display text #} - {{ verse.ref }} + {{ reference }} {% endif %} - {% else %} - {{ verse.ref }} {% endif %} - {% if verse.note %} + {% if verse.note is defined and verse.note %} — {{ verse.note }} {% endif %} + {% if verse.text is defined and verse.text %} +
    + {{ verse.text }} +
    + {% endif %}
  • {% endfor %} diff --git a/kjvstudy_org/templates/topic_pdf.html b/kjvstudy_org/templates/topic_pdf.html index b43ea55..f1576f8 100644 --- a/kjvstudy_org/templates/topic_pdf.html +++ b/kjvstudy_org/templates/topic_pdf.html @@ -97,8 +97,17 @@ diff --git a/kjvstudy_org/topics.py b/kjvstudy_org/topics.py index 3d0a25f..f06404a 100644 --- a/kjvstudy_org/topics.py +++ b/kjvstudy_org/topics.py @@ -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,