Files
kjvstudy.org/kjvstudy_org/templates/study_guide_detail.html
T
kennethreitz 3336863a4d Improve keyboard navigation consistency across site
- Add KJVNav.initGridNav for standardized 2D grid navigation
- Migrate books.html, topics.html, resources.html to use initGridNav
- Add sidebarActive check to all templates with custom keyboard handlers
- Add [ and ] shortcuts for prev/next chapter on chapter pages
- Add [ and ] shortcuts for prev/next book on book pages
- Update accessibility page with comprehensive keyboard shortcut docs
- Add honest note about keyboard navigation complexity
- Fix sidebar nav conflicting with main content selection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 01:26:09 -05:00

292 lines
10 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ guide.title }} - Bible Study Guide - KJV Bible{% endblock %}
{% block description %}{{ guide.description }}{% endblock %}
{% block og_type %}article{% endblock %}
{% block og_title %}{{ guide.title }} - Bible Study Guide{% endblock %}
{% block og_description %}{{ guide.description }}{% endblock %}
{% block structured_data %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": {{ guide.title | tojson }},
"description": {{ guide.description | tojson }},
"author": {
"@type": "Organization",
"name": "KJV Study"
},
"publisher": {
"@type": "Organization",
"name": "KJV Study",
"url": "https://kjvstudy.org"
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://kjvstudy.org{{ request.url.path }}"
},
"about": {
"@type": "Thing",
"name": "Bible Study"
}
}
</script>
{% endblock %}
{% block head %}
<style>
.guide-actions {
margin: 1.25rem 0;
}
.guide-download-btn {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.45rem 0.9rem;
font-size: 0.9rem;
color: var(--text-secondary, #666);
background: var(--code-bg, #f8f8f8);
border: 1px solid var(--border-color, #ddd);
border-radius: 4px;
text-decoration: none;
transition: all 0.2s;
}
.guide-download-btn:hover {
background: var(--bg-color, #fff);
border-color: var(--link-color);
color: var(--link-color);
}
.guide-download-btn svg {
width: 16px;
height: 16px;
}
@media print {
.guide-actions,
.guide-download-btn {
display: none;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to link verse references in text
function linkVerseReferences(element) {
if (!element) return;
// Get all text nodes
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
const textNodes = [];
let node;
while (node = walker.nextNode()) {
textNodes.push(node);
}
textNodes.forEach(function(textNode) {
let text = textNode.textContent;
let changed = false;
// First, handle comma-separated verse lists like "Psalms 7:17, 9:2, 18:13"
// Pattern: Book chapter:verse, chapter:verse, chapter:verse...
text = text.replace(/\b(\d?\s?[A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)\s+((?:\d+:\d+(?:-\d+)?(?:\s*,\s*)?)+)/g, function(match, book, verseList) {
book = book.trim();
// Check if this is actually a comma-separated list
if (!verseList.includes(',')) {
// Single verse, will be handled by the next regex
return match;
}
changed = true;
// Split by comma and process each verse reference
var verses = verseList.split(/\s*,\s*/);
var links = verses.map(function(verseRef) {
verseRef = verseRef.trim();
var parts = verseRef.match(/^(\d+):(\d+)(?:-(\d+))?$/);
if (parts) {
var chapter = parts[1];
var verseStart = parts[2];
var verseEnd = parts[3];
if (verseEnd) {
return '<a href="/book/' + book + '/chapter/' + chapter + '#verse-' + verseStart + '-' + verseEnd + '">' + book + ' ' + chapter + ':' + verseStart + '-' + verseEnd + '</a>';
} else {
return '<a href="/book/' + book + '/chapter/' + chapter + '/verse/' + verseStart + '">' + book + ' ' + chapter + ':' + verseStart + '</a>';
}
}
return verseRef;
});
return links.join(', ');
});
// Then handle individual verse references like "John 3:16" or "Romans 8:28-29"
// This regex handles book names with optional numbers and spaces
text = text.replace(/\b(\d?\s?[A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)\s+(\d+):(\d+)(?:-(\d+))?\b/g, function(match, book, chapter, verseStart, verseEnd) {
changed = true;
book = book.trim();
if (verseEnd) {
return '<a href="/book/' + book + '/chapter/' + chapter + '#verse-' + verseStart + '-' + verseEnd + '">' + match + '</a>';
} else {
return '<a href="/book/' + book + '/chapter/' + chapter + '/verse/' + verseStart + '">' + match + '</a>';
}
});
// Handle chapter-only references like "Psalm 2" or "Genesis 1"
// Must come after verse references to avoid double-matching
text = text.replace(/\b(Psalm|Genesis|Exodus|Isaiah|Daniel|Revelation|Matthew|Mark|Luke|John|Romans|Hebrews|Proverbs|Ecclesiastes|Job)\s+(\d+)\b(?!:)/g, function(match, book, chapter) {
changed = true;
return '<a href="/book/' + book + '/chapter/' + chapter + '">' + match + '</a>';
});
if (changed) {
const span = document.createElement('span');
span.innerHTML = text;
textNode.parentNode.replaceChild(span, textNode);
// Replace the span's children with its contents
while (span.firstChild) {
span.parentNode.insertBefore(span.firstChild, span);
}
span.parentNode.removeChild(span);
}
});
}
// Link verse references in all section content paragraphs
document.querySelectorAll('section p').forEach(function(paragraph) {
// Skip paragraphs that are just margin toggle labels
if (!paragraph.querySelector('.margin-toggle')) {
linkVerseReferences(paragraph);
}
});
});
</script>
{% endblock %}
{% block content %}
<h1>{{ guide.title }}</h1>
<p class="subtitle">{{ guide.description }}</p>
<script>
document.body.dataset.resourceReader = 'true';
</script>
{% if pdf_available and pdf_url %}
<div class="guide-actions">
<a href="{{ pdf_url }}" class="guide-download-btn">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Download PDF
</a>
</div>
{% endif %}
<nav class="toc">
<h2>Contents</h2>
<ul>
{% for section in guide.sections %}
<li><a href="#section-{{ loop.index }}">{{ section.title }}</a></li>
{% endfor %}
</ul>
</nav>
{% for section in guide.sections %}
{% set section_index = loop.index %}
<section id="section-{{ section_index }}">
<h2>{{ section.title }}</h2>
{% for verse_data in section.verse_texts %}
<p>
<label for="mn-s{{ section_index }}-v{{ loop.index }}" class="margin-toggle"></label>
<input type="checkbox" id="mn-s{{ section_index }}-v{{ loop.index }}" class="margin-toggle"/>
<span class="marginnote">
<strong><a href="{{ verse_data.url }}">{{ verse_data.reference }}</a></strong><br/>
<em>{{ verse_data.text }}</em>
</span>
</p>
{% endfor %}
{% set paragraphs = section.content.split('<br><br>') %}
{% for paragraph in paragraphs %}
<p class="guide-paragraph" data-section="{{ section_index }}" data-paragraph="{{ loop.index0 }}">{{ paragraph | format_lists | safe }}</p>
{% endfor %}
</section>
{% endfor %}
<nav>
<p>
<a href="/study-guides">← Study Guides</a> |
<a href="/">Home</a>
</p>
</nav>
<script>
(function() {
const paragraphs = Array.from(document.querySelectorAll('.guide-paragraph'));
if (paragraphs.length === 0) return;
let selectedIndex = -1;
function selectParagraph(index) {
if (selectedIndex >= 0 && selectedIndex < paragraphs.length) {
paragraphs[selectedIndex].style.outline = '';
paragraphs[selectedIndex].style.outlineOffset = '';
paragraphs[selectedIndex].classList.remove('selected');
}
selectedIndex = Math.max(0, Math.min(index, paragraphs.length - 1));
paragraphs[selectedIndex].style.outline = '2px solid #4a7c59';
paragraphs[selectedIndex].style.outlineOffset = '4px';
paragraphs[selectedIndex].classList.add('selected');
paragraphs[selectedIndex].scrollIntoView({ behavior: 'auto', block: 'center' });
}
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (KJVNav.sidebarActive) return;
if (e.key === 'ArrowDown' || e.key === 'j') {
e.preventDefault();
if (KJVNav.isSelectionOffScreen(paragraphs, selectedIndex)) {
selectParagraph(KJVNav.findFirstVisibleIndex(paragraphs));
} else {
selectParagraph(selectedIndex < 0 ? 0 : selectedIndex + 1);
}
} else if (e.key === 'ArrowUp' || e.key === 'k') {
e.preventDefault();
if (KJVNav.isSelectionOffScreen(paragraphs, selectedIndex)) {
selectParagraph(KJVNav.findFirstVisibleIndex(paragraphs));
} else if (selectedIndex <= 0) {
selectParagraph(0);
} else {
selectParagraph(selectedIndex - 1);
}
} else if (e.key === 'ArrowLeft' || e.key === 'h') {
e.preventDefault();
history.back();
} else if (e.key === 'Enter' && selectedIndex >= 0) {
e.preventDefault();
const link = paragraphs[selectedIndex].querySelector('a');
if (link) window.location.href = link.href;
} else if (e.key === 'Escape') {
e.preventDefault();
if (selectedIndex >= 0 && selectedIndex < paragraphs.length) {
paragraphs[selectedIndex].style.outline = '';
paragraphs[selectedIndex].style.outlineOffset = '';
paragraphs[selectedIndex].classList.remove('selected');
}
selectedIndex = -1;
}
});
})();
</script>
{% endblock %}