Files
kjvstudy.org/kjvstudy_org/templates/search.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

484 lines
16 KiB
HTML

{% extends "base.html" %}
{% block title %}{% if query %}Search Results for "{{ query }}"{% else %}Search the KJV Bible{% endif %} - KJV Study{% endblock %}
{% block description %}{% if query %}Search results for "{{ query }}" in the King James Bible.{% else %}Search the complete King James Bible.{% endif %}{% endblock %}
{% block head %}
<style>
.search-form {
margin: 2rem 0;
position: relative;
}
.search-input-wrapper {
position: relative;
display: inline-block;
width: 100%;
max-width: 55%;
}
.search-input {
width: 100%;
padding: 0.75rem;
font-size: 1.1rem;
border: 1px solid var(--border-color, #ccc);
border-radius: 4px;
background: var(--bg-color);
color: var(--text-color);
}
.search-input:focus {
outline: none;
border-color: var(--text-color, #111);
}
.search-button {
margin-top: 1rem;
padding: 0.75rem 1.5rem;
background: var(--text-color, #111);
color: var(--bg-color, #fff);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.search-button:hover {
opacity: 0.85;
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--bg-color);
border: 1px solid var(--border-color-darker, #999);
border-top: none;
border-radius: 0 0 4px 4px;
max-height: 400px;
overflow-y: auto;
z-index: 1000;
display: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.search-dropdown.show {
display: block;
}
.search-result-category {
padding: 0.5rem 0.75rem 0.25rem;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-tertiary);
background: var(--code-bg);
border-bottom: 1px solid var(--border-color);
}
.search-result-item {
display: block;
padding: 0.5rem 0.75rem;
text-decoration: none;
color: var(--text-color);
border-bottom: 1px solid var(--border-color);
cursor: pointer;
transition: background 0.15s;
}
.search-result-item:hover,
.search-result-item.selected {
background: var(--code-bg);
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-title {
font-weight: 500;
font-size: 0.95rem;
}
.search-result-subtitle {
font-size: 0.8rem;
color: var(--text-tertiary);
margin-top: 0.1rem;
}
.search-stats {
margin: 2rem 0;
color: #666;
}
.search-result {
margin: 2rem 0;
padding: 1rem;
border-left: 3px solid #111;
}
.result-reference {
font-weight: 600;
margin-bottom: 0.5rem;
}
.result-reference a {
text-decoration: none;
color: #111;
}
.result-reference a:hover {
text-decoration: underline;
}
.result-text {
line-height: 1.6;
}
.result-text mark {
background: #fffacd;
padding: 0.1rem 0.2rem;
}
.search-tips {
margin: 2rem 0;
padding: 1.5rem;
background: #f9f9f9;
border-left: 3px solid #111;
}
.search-tips h3 {
margin-top: 0;
}
.search-tips ul {
line-height: 1.8;
}
</style>
{% endblock %}
{% block content %}
<h1>Search the KJV Bible</h1>
<p class="subtitle">Search across all verses in the Authorized King James Version</p>
<section role="search" aria-label="Bible search">
<form class="search-form" method="get" action="/search" role="search">
<div class="search-input-wrapper">
<input
type="text"
name="q"
id="search-input"
value="{{ query }}"
placeholder="Enter words or phrases to search..."
class="search-input"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
{% if not query %}autofocus{% endif %}
aria-label="Search for Bible verses, topics, and resources"
>
<div id="search-dropdown" class="search-dropdown" role="listbox" aria-live="polite"></div>
</div>
<button type="submit" class="search-button" aria-label="Submit search query">Search</button>
</form>
{% if query %}
{% if total_results > 0 %}
<div class="search-stats" role="status" aria-live="polite">
Found <strong>{{ total_results }}</strong> result{{ 's' if total_results != 1 else '' }} for "<strong>{{ query }}</strong>"
</div>
{% if family_tree_results %}
<h2 style="margin-top: 2rem;" role="heading" aria-level="2">People in Family Tree</h2>
{% for result in family_tree_results %}
<article class="search-result">
<div class="result-reference">
<a href="{{ result.url }}">{{ result.name }}</a>
</div>
<div class="result-text" style="color: #666;">
{{ result.description }}
{% if result.birth_year != "Unknown" or result.death_year != "Unknown" %}
{% if result.birth_year != "Unknown" %}Born {{ result.birth_year }}{% endif %}
{% if result.death_year != "Unknown" %}{% if result.birth_year != "Unknown" %}, {% endif %}Died {{ result.death_year }}{% endif %}
{% endif %}
</div>
</article>
{% endfor %}
{% endif %}
{% if results %}
<h2 style="margin-top: 2rem;">Bible Verses</h2>
{% for result in results %}
<article class="search-result">
<div class="result-reference">
<a href="{{ result.url }}">{{ result.reference }}</a>
</div>
<div class="result-text">{{ result.highlighted_text | link_names | safe }}</div>
</article>
{% endfor %}
{% endif %}
{% elif total_results == 0 %}
<div class="search-stats">
<p><strong>No results found</strong> for "{{ query }}". Try different words or check your spelling.</p>
</div>
{% endif %}
{% endif %}
{% if not query or total_results == 0 %}
<div class="search-tips">
<h3>Search Tips</h3>
<ul>
<li>Search for words or phrases that appear in Bible verses</li>
<li>Enter specific verse references like "John 3:16" or "Genesis 1:1"</li>
<li>Use Roman numerals ("I John 4:8") or numbers ("1 John 4:8") for numbered books</li>
<li>Use multiple words to find verses containing all terms</li>
<li>Use Old English spellings for better KJV results (e.g., "loveth" instead of "loves")</li>
</ul>
</div>
{% endif %}
</section>
<script>
document.addEventListener('DOMContentLoaded', function() {
var searchInput = document.getElementById('search-input');
var dropdown = document.getElementById('search-dropdown');
var currentResults = [];
var selectedIndex = -1;
var searchTimeout = null;
var bookMap = {
'gen': 'Genesis', 'ex': 'Exodus', 'lev': 'Leviticus', 'num': 'Numbers', 'deut': 'Deuteronomy',
'josh': 'Joshua', 'judg': 'Judges', 'ruth': 'Ruth', 'ps': 'Psalms', 'prov': 'Proverbs',
'isa': 'Isaiah', 'jer': 'Jeremiah', 'dan': 'Daniel', 'matt': 'Matthew', 'mk': 'Mark',
'lk': 'Luke', 'jn': 'John', 'acts': 'Acts', 'rom': 'Romans', 'rev': 'Revelation',
'genesis': 'Genesis', 'exodus': 'Exodus', 'matthew': 'Matthew', 'mark': 'Mark',
'luke': 'Luke', 'john': 'John', 'romans': 'Romans', 'revelation': 'Revelation'
};
function capitalizeBook(name) {
return bookMap[name.toLowerCase()] || name;
}
function parseVerseReference(input) {
var match = input.match(/^(.+)\s+(\d+):(\d+)$/i);
if (match) {
var book = capitalizeBook(match[1].trim());
return { url: '/book/' + encodeURIComponent(book) + '/chapter/' + match[2] + '/verse/' + match[3], display: book + ' ' + match[2] + ':' + match[3] };
}
match = input.match(/^(.+)\s+(\d+)$/i);
if (match) {
var book = capitalizeBook(match[1].trim());
return { url: '/book/' + encodeURIComponent(book) + '/chapter/' + match[2], display: book + ' ' + match[2] };
}
return null;
}
function showDropdown(html) {
dropdown.innerHTML = html;
dropdown.classList.add('show');
}
function hideDropdown() {
dropdown.classList.remove('show');
currentResults = [];
selectedIndex = -1;
}
function updateSelection() {
var items = dropdown.querySelectorAll('.search-result-item');
items.forEach(function(item, i) {
if (i === selectedIndex) {
item.classList.add('selected');
} else {
item.classList.remove('selected');
}
});
}
function renderDropdown(query, apiResults) {
var html = '';
currentResults = [];
// Check for verse reference
var verseRef = parseVerseReference(query);
if (verseRef) {
html += '<div class="search-result-category">Go to Verse</div>';
currentResults.push(verseRef.url);
html += '<a href="' + verseRef.url + '" class="search-result-item selected">';
html += '<div class="search-result-title">' + verseRef.display + '</div>';
html += '</a>';
}
// API results
if (apiResults && apiResults.results) {
var results = apiResults.results;
// Books
if (results.books && results.books.length > 0) {
html += '<div class="search-result-category">Books</div>';
results.books.forEach(function(book) {
currentResults.push(book.url);
html += '<a href="' + book.url + '" class="search-result-item">';
html += '<div class="search-result-title">' + book.name + '</div>';
html += '</a>';
});
}
// Topics
if (results.topics && results.topics.length > 0) {
html += '<div class="search-result-category">Topics</div>';
results.topics.forEach(function(topic) {
currentResults.push(topic.url);
html += '<a href="' + topic.url + '" class="search-result-item">';
html += '<div class="search-result-title">' + topic.name + '</div>';
html += '</a>';
});
}
// Resources
if (results.resources && results.resources.length > 0) {
html += '<div class="search-result-category">Resources</div>';
results.resources.forEach(function(resource) {
currentResults.push(resource.url);
html += '<a href="' + resource.url + '" class="search-result-item">';
html += '<div class="search-result-title">' + resource.name + '</div>';
html += '</a>';
});
}
// Stories
if (results.stories && results.stories.length > 0) {
html += '<div class="search-result-category">Stories</div>';
results.stories.forEach(function(story) {
currentResults.push(story.url);
html += '<a href="' + story.url + '" class="search-result-item">';
html += '<div class="search-result-title">' + story.title + '</div>';
html += '</a>';
});
}
// Verses (at the end)
if (results.verses && results.verses.length > 0) {
html += '<div class="search-result-category">Verses</div>';
results.verses.forEach(function(verse) {
currentResults.push(verse.url);
html += '<a href="' + verse.url + '" class="search-result-item">';
html += '<div class="search-result-title">' + verse.reference + '</div>';
html += '<div class="search-result-subtitle">' + verse.text + '</div>';
html += '</a>';
});
}
}
if (html) {
selectedIndex = currentResults.length > 0 ? 0 : -1;
showDropdown(html);
} else {
hideDropdown();
}
}
if (searchInput) {
searchInput.addEventListener('input', function() {
var query = this.value.trim();
clearTimeout(searchTimeout);
if (query.length < 2) {
hideDropdown();
return;
}
// Show verse reference immediately if detected
var verseRef = parseVerseReference(query);
if (verseRef) {
renderDropdown(query, null);
}
// Fetch API results with debounce
searchTimeout = setTimeout(function() {
fetch('/api/universal-search?q=' + encodeURIComponent(query) + '&limit=5')
.then(function(r) { return r.json(); })
.then(function(data) {
renderDropdown(query, data);
})
.catch(function(err) {
console.error('Search error:', err);
});
}, 200);
});
searchInput.addEventListener('keydown', function(e) {
if (!dropdown.classList.contains('show')) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
selectedIndex = Math.min(selectedIndex + 1, currentResults.length - 1);
updateSelection();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
selectedIndex = Math.max(selectedIndex - 1, -1);
updateSelection();
} else if (e.key === 'Enter' && selectedIndex >= 0 && currentResults[selectedIndex]) {
e.preventDefault();
window.location.href = currentResults[selectedIndex];
} else if (e.key === 'Escape') {
hideDropdown();
}
});
// Click outside to close dropdown
document.addEventListener('click', function(e) {
if (!dropdown.contains(e.target) && e.target !== searchInput) {
hideDropdown();
}
});
}
// Keyboard navigation for search results
var searchResults = Array.from(document.querySelectorAll('.search-result'));
if (searchResults.length > 0) {
var resultIndex = -1;
function selectResult(index) {
if (resultIndex >= 0 && resultIndex < searchResults.length) {
searchResults[resultIndex].style.outline = '';
searchResults[resultIndex].style.outlineOffset = '';
}
resultIndex = Math.max(0, Math.min(index, searchResults.length - 1));
searchResults[resultIndex].style.outline = '2px solid #4a7c59';
searchResults[resultIndex].style.outlineOffset = '4px';
searchResults[resultIndex].scrollIntoView({ behavior: 'auto', block: 'center' });
}
document.addEventListener('keydown', function(e) {
// Don't handle when typing in search input or dropdown is open
if (e.target === searchInput || dropdown.classList.contains('show')) return;
// Don't handle if sidebar navigation is active
if (KJVNav.sidebarActive) return;
if (e.key === 'ArrowDown' || e.key === 'j') {
e.preventDefault();
selectResult(resultIndex < 0 ? 0 : resultIndex + 1);
} else if (e.key === 'ArrowUp' || e.key === 'k') {
e.preventDefault();
if (resultIndex <= 0) selectResult(0);
else selectResult(resultIndex - 1);
} else if (e.key === 'ArrowLeft' || e.key === 'h') {
e.preventDefault();
history.back();
} else if (e.key === 'Enter' && resultIndex >= 0) {
e.preventDefault();
var link = searchResults[resultIndex].querySelector('.result-reference a');
if (link) window.location.href = link.href;
}
});
}
});
</script>
{% endblock %}