231 lines
7.8 KiB
JavaScript
231 lines
7.8 KiB
JavaScript
function getAppBase() {
|
|
const { pathname } = window.location;
|
|
if (pathname === "/" || pathname === "/index.html") {
|
|
return "";
|
|
}
|
|
if (pathname.endsWith("/index.html")) {
|
|
return pathname.slice(0, -"/index.html".length);
|
|
}
|
|
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
}
|
|
|
|
const apiBase = getAppBase().replace(/\/bibliography\.html$/, "");
|
|
const bibliographyList = document.querySelector("#bibliography-list");
|
|
const bibliographySearch = document.querySelector("#bibliography-search");
|
|
const bibliographyStatus = document.querySelector("#bibliography-status");
|
|
const bibliographyDownload = document.querySelector("#bibliography-download");
|
|
let currentBibliographyItems = [];
|
|
|
|
function escapeHtml(value) {
|
|
return String(value)
|
|
.replaceAll("&", "&")
|
|
.replaceAll('"', """)
|
|
.replaceAll("<", "<")
|
|
.replaceAll(">", ">");
|
|
}
|
|
|
|
function normalizeAbstractForDisplay(value) {
|
|
const raw = String(value || "").trim();
|
|
if (!raw) {
|
|
return "";
|
|
}
|
|
const temp = document.createElement("div");
|
|
temp.innerHTML = raw;
|
|
return temp.textContent
|
|
.replace(/^abstract\s*[:.\-]?\s*/i, "")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
function parseBibtexFields(draftBibtex) {
|
|
const fields = {};
|
|
const text = String(draftBibtex || "");
|
|
const pattern = /([a-zA-Z_]+)\s*=\s*\{([^}]*)\}/g;
|
|
let match = pattern.exec(text);
|
|
while (match) {
|
|
fields[match[1].toLowerCase()] = match[2].trim();
|
|
match = pattern.exec(text);
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
function collectBibtexRecords(items) {
|
|
const seen = new Set();
|
|
const records = [];
|
|
for (const item of items || []) {
|
|
const draftBibtex = String(item && item.draft_bibtex ? item.draft_bibtex : "").trim();
|
|
if (!draftBibtex || seen.has(draftBibtex)) {
|
|
continue;
|
|
}
|
|
seen.add(draftBibtex);
|
|
records.push(draftBibtex);
|
|
}
|
|
return records;
|
|
}
|
|
|
|
function downloadBibtexRecords(items, filenameStem) {
|
|
const records = collectBibtexRecords(items);
|
|
if (!records.length) {
|
|
return false;
|
|
}
|
|
const blob = new Blob([`${records.join("\n\n")}\n`], { type: "application/x-bibtex;charset=utf-8" });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
link.download = `${filenameStem}.bib`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
window.setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
return true;
|
|
}
|
|
|
|
function syncDownloadButton(items) {
|
|
if (!bibliographyDownload) {
|
|
return;
|
|
}
|
|
const recordCount = collectBibtexRecords(items).length;
|
|
bibliographyDownload.disabled = !recordCount;
|
|
bibliographyDownload.textContent = recordCount
|
|
? `Download BibTeX (${recordCount})`
|
|
: "Download BibTeX";
|
|
}
|
|
|
|
function buildCitationText(item) {
|
|
const fields = parseBibtexFields(item.draft_bibtex || "");
|
|
if (item.normalized_text) {
|
|
return escapeHtml(item.normalized_text);
|
|
}
|
|
const author = fields.author || "";
|
|
const year = fields.year || "";
|
|
const title = fields.title || "";
|
|
const venue = fields.journal || fields.booktitle || fields.publisher || "";
|
|
const volume = fields.volume || "";
|
|
const issue = fields.number || "";
|
|
const pages = fields.pages || "";
|
|
const parts = [];
|
|
const lead = [author, year ? `(${year})` : ""].filter(Boolean).join(" ");
|
|
if (lead) {
|
|
parts.push(lead);
|
|
}
|
|
if (title) {
|
|
parts.push(title);
|
|
}
|
|
const venueBits = [venue, volume ? `${volume}${issue ? `(${issue})` : ""}` : issue ? `(${issue})` : "", pages]
|
|
.filter(Boolean)
|
|
.join(", ");
|
|
if (venueBits) {
|
|
parts.push(venueBits);
|
|
}
|
|
return escapeHtml(parts.join(". ").trim() || item.raw_text || "");
|
|
}
|
|
|
|
function renderSpeciesRefs(refs) {
|
|
return refs
|
|
.map(
|
|
(ref) =>
|
|
`<a href="./index.html#${escapeHtml(ref.slug)}">${escapeHtml(ref.common_name || ref.slug)}</a>`,
|
|
)
|
|
.join(", ");
|
|
}
|
|
|
|
function renderAbstractBlock(text) {
|
|
const abstract = normalizeAbstractForDisplay(text);
|
|
if (!abstract) {
|
|
return "";
|
|
}
|
|
return `
|
|
<div class="citation-abstract-shell">
|
|
<button type="button" class="secondary-button citation-abstract-toggle" aria-expanded="false">
|
|
Show Abstract
|
|
</button>
|
|
<div class="citation-abstract-display hidden">
|
|
<p class="public-citation-abstract">${escapeHtml(abstract)}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function attachCitationAbstractToggles(root) {
|
|
for (const toggle of root.querySelectorAll(".citation-abstract-toggle")) {
|
|
const shell = toggle.parentElement;
|
|
const display = shell && shell.querySelector(".citation-abstract-display");
|
|
if (!display) {
|
|
continue;
|
|
}
|
|
toggle.addEventListener("click", () => {
|
|
const hidden = display.classList.toggle("hidden");
|
|
toggle.setAttribute("aria-expanded", hidden ? "false" : "true");
|
|
toggle.textContent = hidden ? "Show Abstract" : "Hide Abstract";
|
|
});
|
|
}
|
|
}
|
|
|
|
function renderBibliography(items) {
|
|
bibliographyList.innerHTML = "";
|
|
if (!items.length) {
|
|
bibliographyList.innerHTML = `<p class="editor-status">No bibliography entries match the current search.</p>`;
|
|
return;
|
|
}
|
|
|
|
for (const item of items) {
|
|
const links = [
|
|
item.doi ? `<a href="https://doi.org/${encodeURIComponent(String(item.doi).replace(/^https?:\/\/doi\.org\//, ""))}" target="_blank" rel="noopener noreferrer">DOI</a>` : "",
|
|
item.source_url ? `<a href="${escapeHtml(item.source_url)}" target="_blank" rel="noopener noreferrer">Source</a>` : "",
|
|
item.openalex_id ? `<a href="https://openalex.org/${escapeHtml(String(item.openalex_id).replace(/^https?:\/\/openalex\.org\//, ""))}" target="_blank" rel="noopener noreferrer">OpenAlex</a>` : "",
|
|
]
|
|
.filter(Boolean)
|
|
.join(" · ");
|
|
|
|
const article = document.createElement("article");
|
|
article.className = "public-citation-entry";
|
|
article.innerHTML = `
|
|
<p class="public-citation-text">${buildCitationText(item)}</p>
|
|
${renderAbstractBlock(item.abstract_text || "")}
|
|
<p class="public-citation-meta">
|
|
Appears in ${item.species_count} species record${item.species_count === 1 ? "" : "s"}
|
|
${item.legacy_reference_numbers && item.legacy_reference_numbers.length ? ` • Imported references: ${item.legacy_reference_numbers.map((value) => escapeHtml(value)).join(", ")}` : ""}
|
|
</p>
|
|
<p class="public-citation-meta">Species: ${renderSpeciesRefs(item.species_refs || [])}</p>
|
|
${links ? `<p class="public-citation-links">${links}</p>` : ""}
|
|
`;
|
|
attachCitationAbstractToggles(article);
|
|
bibliographyList.appendChild(article);
|
|
}
|
|
}
|
|
|
|
async function loadBibliography(search = "") {
|
|
bibliographyStatus.textContent = "Loading bibliography...";
|
|
const query = search ? `?search=${encodeURIComponent(search)}` : "";
|
|
const response = await fetch(`${apiBase}/api/bibliography${query}`);
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
bibliographyList.innerHTML = `<p class="error">${escapeHtml(data.error || "Unable to load bibliography.")}</p>`;
|
|
bibliographyStatus.textContent = data.error || "Bibliography load failed";
|
|
return;
|
|
}
|
|
|
|
currentBibliographyItems = data.items || [];
|
|
renderBibliography(currentBibliographyItems);
|
|
syncDownloadButton(currentBibliographyItems);
|
|
bibliographyStatus.textContent = `${data.count || 0} bibliography entr${data.count === 1 ? "y" : "ies"}`;
|
|
}
|
|
|
|
bibliographySearch.addEventListener("input", async (event) => {
|
|
await loadBibliography(event.target.value);
|
|
});
|
|
|
|
loadBibliography().catch((error) => {
|
|
bibliographyList.innerHTML = `<p class="error">Failed to load bibliography: ${escapeHtml(String(error))}</p>`;
|
|
bibliographyStatus.textContent = "Bibliography load failed";
|
|
});
|
|
|
|
if (bibliographyDownload) {
|
|
bibliographyDownload.addEventListener("click", () => {
|
|
const downloaded = downloadBibtexRecords(currentBibliographyItems, "ecospecies-bibliography");
|
|
if (!downloaded) {
|
|
bibliographyStatus.textContent = "No BibTeX records are available for download yet.";
|
|
}
|
|
});
|
|
}
|