224 lines
7.8 KiB
Text
224 lines
7.8 KiB
Text
---
|
|
import SiteLayout from "../../components/SiteLayout.astro";
|
|
import { getSpeechArchive, uniqueSorted } from "../../lib/speeches";
|
|
|
|
const speeches = getSpeechArchive();
|
|
const years = uniqueSorted(speeches.map((speech) => speech.year), "desc");
|
|
const speakers = uniqueSorted(speeches.map((speech) => speech.speaker));
|
|
---
|
|
|
|
<SiteLayout
|
|
title="Speeches Archive – FFWPU Ireland"
|
|
description="Search the FFWPU Ireland archive of speeches by Rev. Sun Myung Moon and Dr. Hak Ja Han Moon by year, speaker, and text."
|
|
bodyClass="page-template page-template-templates page-template-template-onecolumn page-template-templatestemplate-onecolumn-php page custom-background tribe-no-js metaslider-plugin parabola-image-three caption-light meta-light parabola_triagles parabola-menu-left"
|
|
pathname="/speeches/"
|
|
canonical="/speeches/"
|
|
>
|
|
<div id="main">
|
|
<div id="forbottom">
|
|
<div style="clear:both;"> </div>
|
|
|
|
<section id="container" class="one-column speeches-archive">
|
|
<div id="content" role="main">
|
|
<div class="post page type-page status-publish hentry">
|
|
<h1 class="entry-title">Speeches Archive</h1>
|
|
|
|
<div class="entry-content">
|
|
<form class="speech-filters" id="speech-filters" role="search">
|
|
<div class="speech-filter speech-filter-search">
|
|
<label for="speech-search">Search text</label>
|
|
<input id="speech-search" name="q" type="search" autocomplete="off" placeholder="Search titles and full speech text" />
|
|
</div>
|
|
|
|
<div class="speech-filter">
|
|
<label for="speech-year">Year</label>
|
|
<select id="speech-year" name="year">
|
|
<option value="">All years</option>
|
|
{years.map((year) => <option value={year}>{year}</option>)}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="speech-filter">
|
|
<label for="speech-speaker">Speaker</label>
|
|
<select id="speech-speaker" name="speaker">
|
|
<option value="">All speakers</option>
|
|
{speakers.map((speaker) => <option value={speaker}>{speaker}</option>)}
|
|
</select>
|
|
</div>
|
|
|
|
<button class="speech-reset" type="reset">Reset filters</button>
|
|
</form>
|
|
|
|
<div class="speech-archive-status" aria-live="polite">
|
|
<strong id="speech-result-count">{speeches.length}</strong> speeches
|
|
</div>
|
|
|
|
<div class="speech-results" id="speech-results">
|
|
{
|
|
speeches.map((speech) => (
|
|
<article class="speech-result">
|
|
<div class="speech-result-meta">
|
|
<span>{speech.year}</span>
|
|
<span>{speech.speaker}</span>
|
|
</div>
|
|
<h2 class="speech-result-title">
|
|
<a href={speech.url}>{speech.title}</a>
|
|
</h2>
|
|
<p>{speech.excerpt}</p>
|
|
</article>
|
|
))
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div style="clear:both;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script is:inline slot="scripts">
|
|
(() => {
|
|
const form = document.getElementById("speech-filters");
|
|
const results = document.getElementById("speech-results");
|
|
const count = document.getElementById("speech-result-count");
|
|
const searchInput = document.getElementById("speech-search");
|
|
const yearSelect = document.getElementById("speech-year");
|
|
const speakerSelect = document.getElementById("speech-speaker");
|
|
|
|
if (!form || !results || !count || !searchInput || !yearSelect || !speakerSelect) {
|
|
return;
|
|
}
|
|
|
|
let speeches = [];
|
|
let renderTimer = 0;
|
|
|
|
function normalize(value) {
|
|
return value
|
|
.normalize("NFKD")
|
|
.replace(/[\u0300-\u036f]/g, "")
|
|
.toLowerCase()
|
|
.trim();
|
|
}
|
|
|
|
function selectedFilters() {
|
|
return {
|
|
q: searchInput.value.trim(),
|
|
year: yearSelect.value,
|
|
speaker: speakerSelect.value,
|
|
};
|
|
}
|
|
|
|
function syncUrl(filters) {
|
|
const params = new URLSearchParams();
|
|
Object.entries(filters).forEach(([key, value]) => {
|
|
if (value) {
|
|
params.set(key, value);
|
|
}
|
|
});
|
|
|
|
const nextUrl = params.toString() ? `${window.location.pathname}?${params}` : window.location.pathname;
|
|
window.history.replaceState(null, "", nextUrl);
|
|
}
|
|
|
|
function hydrateFromUrl() {
|
|
const params = new URLSearchParams(window.location.search);
|
|
searchInput.value = params.get("q") || "";
|
|
yearSelect.value = params.get("year") || "";
|
|
speakerSelect.value = params.get("speaker") || "";
|
|
}
|
|
|
|
function matchesSpeech(speech, filters) {
|
|
if (filters.year && speech.year !== filters.year) {
|
|
return false;
|
|
}
|
|
if (filters.speaker && speech.speaker !== filters.speaker) {
|
|
return false;
|
|
}
|
|
if (!filters.q) {
|
|
return true;
|
|
}
|
|
|
|
const terms = normalize(filters.q).split(/\s+/).filter(Boolean);
|
|
return terms.every((term) => speech.searchText.includes(term));
|
|
}
|
|
|
|
function appendMeta(parent, values) {
|
|
values.filter(Boolean).forEach((value) => {
|
|
const span = document.createElement("span");
|
|
span.textContent = value;
|
|
parent.append(span);
|
|
});
|
|
}
|
|
|
|
function renderSpeech(speech) {
|
|
const article = document.createElement("article");
|
|
article.className = "speech-result";
|
|
|
|
const meta = document.createElement("div");
|
|
meta.className = "speech-result-meta";
|
|
appendMeta(meta, [speech.year, speech.speaker]);
|
|
|
|
const title = document.createElement("h2");
|
|
title.className = "speech-result-title";
|
|
const link = document.createElement("a");
|
|
link.href = speech.url;
|
|
link.textContent = speech.title;
|
|
title.append(link);
|
|
|
|
const excerpt = document.createElement("p");
|
|
excerpt.textContent = speech.excerpt;
|
|
|
|
article.append(meta, title, excerpt);
|
|
return article;
|
|
}
|
|
|
|
function render() {
|
|
const filters = selectedFilters();
|
|
const matched = speeches.filter((speech) => matchesSpeech(speech, filters));
|
|
const fragment = document.createDocumentFragment();
|
|
|
|
if (matched.length) {
|
|
matched.forEach((speech) => fragment.append(renderSpeech(speech)));
|
|
} else {
|
|
const empty = document.createElement("p");
|
|
empty.className = "speech-empty-state";
|
|
empty.textContent = "No speeches match these filters.";
|
|
fragment.append(empty);
|
|
}
|
|
|
|
results.replaceChildren(fragment);
|
|
count.textContent = matched.length.toString();
|
|
syncUrl(filters);
|
|
}
|
|
|
|
function queueRender() {
|
|
window.clearTimeout(renderTimer);
|
|
renderTimer = window.setTimeout(render, 120);
|
|
}
|
|
|
|
hydrateFromUrl();
|
|
|
|
fetch("/speeches/search-index.json")
|
|
.then((response) => {
|
|
if (!response.ok) {
|
|
throw new Error(`Speech index request failed: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then((data) => {
|
|
speeches = data;
|
|
render();
|
|
form.addEventListener("input", queueRender);
|
|
form.addEventListener("change", render);
|
|
form.addEventListener("reset", () => {
|
|
window.setTimeout(render, 0);
|
|
});
|
|
})
|
|
.catch(() => {
|
|
count.textContent = results.querySelectorAll(".speech-result").length.toString();
|
|
});
|
|
})();
|
|
</script>
|
|
</SiteLayout>
|