Illustrations/web/templates/search.html
Joshua Laymon 69943c2717 Update web/templates/search.html
Before fully implementing illustration of the day
2025-08-18 22:10:06 +00:00

282 lines
9.6 KiB
HTML

{% extends "base.html" %}
{% block body_class %}themed-bg{% endblock %}
{% load static %}
{% block content %}
<div class="container">
<form method="get" class="search-form">
<h1 class="page-title">Illustration Search</h1>
<div class="search-row">
<input type="text" name="q" value="{{ q }}" placeholder="Type to search…" class="search-input" autofocus>
<button class="btn btn-primary">Search</button>
<!-- Help button -->
<button
class="btn btn-secondary help-toggle"
type="button"
data-target="#search-help-panel"
>
Help
</button>
</div>
<!-- Help panel -->
<div id="search-help-panel" class="help-panel">
<h3>How to Use Search Operators</h3>
<ul>
<li><strong>Simple keyword</strong> — type any word to find entries that contain it.<br>
<em>Example:</em> <code>faith</code></li>
<li><strong>Phrase search</strong> — put quotes around a phrase to match it exactly.<br>
<em>Example:</em> <code>"Jehovah is my shepherd"</code></li>
<li><strong>OR search</strong> — use <code>OR</code> (uppercase).<br>
<em>Example:</em> <code>love OR kindness</code></li>
<li><strong>Exclude terms</strong> — use <code>-</code> to remove.<br>
<em>Example:</em> <code>hope -future</code></li>
<li><strong>Wildcard search</strong> — use <code>*</code>.<br>
<em>Example:</em> <code>lov*</code></li>
<li><strong>Scripture search</strong> — type a Bible book.<br>
<em>Example:</em> <code>John 3:16</code></li>
</ul>
</div>
<div class="filter-row">
{% for f in field_options %}
<label class="check-pill">
<input type="checkbox" name="{{ f.name }}" {% if f.checked %}checked{% endif %}>
<span>{{ f.label }}</span>
</label>
{% endfor %}
</div>
</form>
{% if ran_search and result_count == 0 %}
<div class="empty-state">
<div class="empty-title">No results</div>
<div class="empty-subtitle">Try broadening your terms or enabling more fields above.</div>
</div>
{% endif %}
<!-- ===== History Dropdown ===== -->
<div class="card" style="padding:0; margin:16px 0 12px;">
<h1 class="page-title dropdown-toggle history-title"
data-target="#history-panel"
role="button"
aria-expanded="false">
Search History <span class="chevron"></span>
</h1>
<div id="history-panel" class="dropdown-panel">
<hr style="border:none; border-top:1px solid var(--border); margin:12px 0;">
<!-- Recent Searches -->
<div style="margin-top:6px;">
<div class="small muted" style="margin-bottom:6px;">Your Recent Searches</div>
<ul id="searchHistoryList" class="small" style="margin:0; padding-left:18px;"></ul>
<div id="searchHistoryEmpty" class="muted small" style="display:none;">No history yet.</div>
</div>
<hr style="border:none; border-top:1px solid var(--border); margin:12px 0;">
<!-- Recently Viewed -->
<div>
<div class="small muted" style="margin-bottom:6px;">Recently Viewed Illustrations</div>
<ul id="recentViewsList" class="small" style="margin:0; padding-left:18px;"></ul>
<div id="recentViewsEmpty" class="muted small" style="display:none;">Nothing yet—open an illustration and linger 10s.</div>
</div>
</div>
</div>
<div class="container">
<form method="get" class="search-form">
<h1 class="page-title">Illustration of the Day</h1>
<!-- insert illustration of the day here -->
</div>
</form>
</div>
<script>
(function(){
// --- CSRF helper ---
function getCookie(name){
const m = document.cookie.match('(^|;)\\s*'+name+'\\s*=\\s*([^;]+)');
return m ? m.pop() : '';
}
const csrftoken = getCookie('csrftoken');
// Build entry view URL
const ENTRY_URL_TEMPLATE = "{% url 'entry_view' 1 %}";
function entryUrlFor(id){
return ENTRY_URL_TEMPLATE.replace(/\/\d+\/?$/, '/' + id + '/');
}
// First N words
function firstWords(text, n){
const clean = (text || '').replace(/\s+/g, ' ').trim();
if (!clean) return '';
const words = clean.split(' ');
if (words.length <= n) return clean;
return words.slice(0, n).join(' ') + '…';
}
// Font prefs
function applyFont(size){
const root = document.documentElement;
root.classList.remove('fs-small','fs-default','fs-large','fs-xlarge');
root.classList.add('fs-'+size);
}
fetch("{% url 'api_get_prefs' %}", { cache: 'no-store', credentials: 'same-origin' })
.then(r=>r.json()).then(j=>{
if (j.ok) applyFont(j.font_size || 'default');
}).catch(()=>{});
// Small helper to add a cache-busting param
function bust(url){
const sep = url.includes('?') ? '&' : '?';
return url + sep + 't=' + Date.now();
}
// Search history (keeps selected-fields subtitle)
function renderHistory(items){
const list = document.getElementById('searchHistoryList');
const empty = document.getElementById('searchHistoryEmpty');
list.innerHTML = '';
if (!items || !items.length){ empty.style.display='block'; return; }
empty.style.display='none';
items.forEach(it=>{
const params = new URLSearchParams();
if (it.q) params.set('q', it.q);
const sel = it.selected || {};
Object.keys(sel).forEach(k=>{ if (sel[k]) params.set(k, 'on'); });
const li = document.createElement('li');
const selectedList = Object.keys(sel).filter(k=>sel[k]).join(', ');
li.innerHTML = `<a href="{% url 'search' %}?${params.toString()}"><strong>${it.q || '(blank)'}</strong></a>
<span class="muted">— ${selectedList}</span>`;
list.appendChild(li);
});
}
function refetchHistory(){
fetch(bust("{% url 'api_get_search_history' %}"), {
cache: 'no-store',
credentials: 'same-origin'
}).then(r=>r.json()).then(j=>{
if (j.ok) renderHistory(j.items);
}).catch(()=>{});
}
refetchHistory();
// Recently viewed
function renderRecent(items){
const list = document.getElementById('recentViewsList');
const empty = document.getElementById('recentViewsEmpty');
list.innerHTML = '';
if (!items || !items.length){ empty.style.display='block'; return; }
empty.style.display='none';
items.forEach(it=>{
const url = entryUrlFor(it.entry_id);
const snippet = (it.snippet && it.snippet.trim())
|| firstWords(it.illustration || '', 20)
|| `Entry #${it.entry_id}`;
const when = new Date(it.viewed_at);
const li = document.createElement('li');
li.innerHTML = `<a href="${url}">${snippet}</a>
<span class="muted"> — ${when.toLocaleString()}</span>`;
list.appendChild(li);
});
}
function refetchRecent(){
fetch(bust("{% url 'api_get_recent_views' %}"), {
cache: 'no-store',
credentials: 'same-origin'
}).then(r=>r.json()).then(j=>{
if (j.ok) renderRecent(j.items);
}).catch(()=>{});
}
refetchRecent();
// ✅ Log searches with Beacon on submit (this is what was missing)
const searchForm = document.querySelector('form.search-form');
if (searchForm){
searchForm.addEventListener('submit', ()=>{
try{
const fd = new FormData(searchForm);
const data = new URLSearchParams();
data.append('q', (fd.get('q') || '').trim());
// selected checkboxes (match your field names)
['subject','illustration','application','scripture_raw','source','talk_title','talk_number','entry_code']
.forEach(k=>{
if (fd.get(k)) data.append(`sel[${k}]`, 'on');
});
const blob = new Blob([data.toString()], { type:'application/x-www-form-urlencoded' });
navigator.sendBeacon("{% url 'api_log_search' %}", blob);
}catch(_){}
});
}
// Dropdown toggle for History
document.querySelectorAll('.dropdown-toggle').forEach(btn=>{
btn.addEventListener('click', ()=>{
const target = document.querySelector(btn.dataset.target);
if (target){
target.classList.toggle('open');
btn.classList.toggle('open');
}
});
});
// Help panel toggle (unchanged)
document.addEventListener('click', function(e){
const btn = e.target.closest('.help-toggle');
if (btn) {
const panel = document.querySelector(btn.dataset.target || '#search-help-panel');
if (panel) panel.classList.toggle('open');
return;
}
const panel = document.querySelector('#search-help-panel');
if (!panel) return;
if (panel.classList.contains('open')) {
const clickedInside = panel.contains(e.target) || e.target.closest('.help-toggle');
if (!clickedInside) panel.classList.remove('open');
}
});
})();
</script>
<style>
/* Help panel */
.help-panel{
display:none;
margin-top:10px;
background:#fff;
border:1px solid var(--border);
border-radius:12px;
padding:14px;
box-shadow:0 4px 16px rgba(0,0,0,.06);
font-size:14px;
}
.help-panel.open{ display:block; }
/* Dropdown panel */
.dropdown-panel { display:none; padding:12px; }
.dropdown-panel.open { display:block; }
/* Chevron style sized for heading */
.page-title .chevron {
font-size: 0.9em;
margin-left: 8px;
vertical-align: middle;
transition: transform 0.2s ease;
display:inline-block;
}
.page-title.open .chevron { transform: rotate(180deg); }
.dropdown-toggle { cursor:pointer; }
/* Keep the chevron next to the text */
.history-title{
display:inline-flex !important;
align-items:center;
justify-content:flex-start !important;
gap:8px;
padding:10px 16px;
margin:0;
}
</style>
{% endblock %}