Update web/templates/search.html

This commit is contained in:
2025-08-18 22:19:48 +00:00
parent 1311bd25fd
commit 2651b65a70
+29 -31
View File
@@ -247,7 +247,7 @@
} }
}); });
/* =============================== /* ===============================
Illustration of the Day (client-only, deterministic) Illustration of the Day (client-only, deterministic)
=============================== */ =============================== */
(function illustrationOfTheDay(){ (function illustrationOfTheDay(){
@@ -266,50 +266,47 @@
const today = new Date(); const today = new Date();
const ymd = today.getFullYear()*10000 + (today.getMonth()+1)*100 + today.getDate(); const ymd = today.getFullYear()*10000 + (today.getMonth()+1)*100 + today.getDate();
// Simple string hash -> 32-bit -> xorshift for decent spread // 32bit xorshift PRNG for a good daily seed
function xorshift32(seed){ function xorshift32(seed){
let x = seed | 0; let x = seed | 0;
x ^= x << 13; x ^= x >>> 17; x ^= x << 5; x ^= x << 13; x ^= x >>> 17; x ^= x << 5;
return (x >>> 0); return (x >>> 0);
} }
function pickIndex(max, seed){ const seed = xorshift32(ymd ^ 0x9E3779B9);
// (0..max-1)
return xorshift32(seed) % max; // We dont know ID gaps, so try a deterministic pseudorandom
// permutation over a *generous* range and stop at the first 200.
const maxGuess = Math.max(100, Math.floor(total * 4)); // cast a wide net
const maxAttempts = Math.min(600, maxGuess); // cap network work
// Linear congruential generator style “index -> id” mapping (deterministic)
function idAt(i){
// multiplier is odd; modulus is implicit 2^32, then map to [1..maxGuess]
const v = (Math.imul(i + 1, 1103515245) + 12345 + seed) >>> 0;
return 1 + (v % maxGuess);
} }
// We dont know if IDs are contiguous, so: async function fetchEntryHtml(id){
// 1) choose a *candidate* ID in [1..maxIdGuess]
// 2) try up to N nearby IDs until one 200s
// Best guess for max id is "total * 1.5" (cheap heuristic).
const maxIdGuess = Math.max(total, Math.floor(total * 1.5));
let candidate = 1 + pickIndex(maxIdGuess, ymd ^ 0x9E3779B9);
async function tryFetch(id){
const url = entryUrlFor(id); const url = entryUrlFor(id);
try{ try{
const r = await fetch(url, { credentials:'same-origin' }); const r = await fetch(url, { credentials: 'same-origin' });
if (r.ok) { if (r.ok) return await r.text();
const html = await r.text();
return { ok:true, id, html };
}
}catch(_){} }catch(_){}
return { ok:false }; return null;
} }
function ensurePunct(str){ function ensurePunct(str){
const s = (str || '').trim(); const s = (str || '').trim();
if (!s) return ''; if (!s) return '';
// If ends with punctuation, keep; else add period.
return /[.!?…]$/.test(s) ? s : (s + '.'); return /[.!?…]$/.test(s) ? s : (s + '.');
} }
function extractSectionText(doc, label){ function extractSectionText(doc, label){
// Look for a section with a label like "Illustration" / "Application" // Look for label “Illustration / Application” commonly used in your templates
const labels = doc.querySelectorAll('.section-label, .meta-label, h3, h4, strong'); const labels = doc.querySelectorAll('.section-label, .meta-label, h3, h4, strong');
for (const el of labels){ for (const el of labels){
const t = (el.textContent || '').trim().toLowerCase(); const t = (el.textContent || '').trim().toLowerCase();
if (t === label){ if (t === label){
// usual structure: label inside a .section; body nearby
const section = el.closest('.section') || el.parentElement; const section = el.closest('.section') || el.parentElement;
if (section){ if (section){
const body = section.querySelector('.lead-text') || const body = section.querySelector('.lead-text') ||
@@ -319,7 +316,7 @@
} }
} }
} }
// Fallback: first .lead-text / .section-body on page if label search failed // fallback
const alt = doc.querySelector('.lead-text, .section-body'); const alt = doc.querySelector('.lead-text, .section-body');
return alt ? (alt.textContent || '').trim() : ''; return alt ? (alt.textContent || '').trim() : '';
} }
@@ -328,22 +325,23 @@
const dom = new DOMParser().parseFromString(html, 'text/html'); const dom = new DOMParser().parseFromString(html, 'text/html');
const illustration = extractSectionText(dom, 'illustration'); const illustration = extractSectionText(dom, 'illustration');
const application = extractSectionText(dom, 'application'); const application = extractSectionText(dom, 'application');
const merged = (ensurePunct(illustration) + (application ? ' ' + application : '')).trim(); const merged = (ensurePunct(illustration) + (application ? ' ' + application : '')).trim();
iotdTextEl.textContent = merged || 'Open to view todays illustration.'; iotdTextEl.textContent = merged || 'Open to view todays illustration.';
iotdOpenEl.href = entryUrlFor(id); iotdOpenEl.href = entryUrlFor(id);
iotdOpenEl.style.display = 'inline-block';
} }
(async function findAndRender(){ (async function findAndRender(){
// Try candidate, then probe up to +/- 10 around it for (let i = 0; i < maxAttempts; i++){
const offsets = [0,1,-1,2,-2,3,-3,4,-4,5,-5,6,-6,7,-7,8,-8,9,-9,10,-10]; const id = idAt(i);
for (const off of offsets){ const html = await fetchEntryHtml(id);
const id = Math.max(1, candidate + off); if (html){
const res = await tryFetch(id); renderIotd(id, html);
if (res.ok) { renderIotd(id, res.html); return; } return;
}
} }
// Give up—show a graceful fallback // If nothing responded 200 within our attempts, fail gracefully
iotdTextEl.textContent = 'Unable to load todays illustration.'; iotdTextEl.textContent = 'Unable to load todays illustration.';
iotdOpenEl.style.display = 'none'; iotdOpenEl.style.display = 'none';
})(); })();