(function initSearchResultsPage() { const API_ENDPOINT = "https://app.litenews.cn/v1/app/search/special"; const API_ORG_ID = "275"; const API_PAGE_SIZE = 10; let activeRequestToken = 0; function escapeHtml(value) { return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function highlightText(text, keyword) { if (!keyword) { return escapeHtml(text); } const matcher = new RegExp(escapeRegExp(keyword), "gi"); let cursor = 0; let output = ""; let match; while ((match = matcher.exec(text)) !== null) { output += escapeHtml(text.slice(cursor, match.index)); output += `${escapeHtml(match[0])}`; cursor = match.index + match[0].length; } output += escapeHtml(text.slice(cursor)); return output; } function getSearchState() { const url = new URL(window.location.href); const keyword = (url.searchParams.get("keyword") || "").trim(); const page = Math.max(1, Number.parseInt(url.searchParams.get("page") || "1", 10) || 1); return { keyword, page }; } function buildUrl(keyword, page) { const url = new URL(window.location.href); if (keyword) { url.searchParams.set("keyword", keyword); url.searchParams.set("page", String(page)); } else { url.searchParams.delete("keyword"); url.searchParams.delete("page"); } return url; } function setLoadingState(isLoading) { const submit = document.querySelector(".search-results-submit"); const input = document.querySelector("[data-search-input]"); if (submit) { submit.disabled = isLoading; submit.textContent = isLoading ? "搜索中" : "搜索"; } if (input) { input.disabled = isLoading; } } function createPaginationButton(label, options) { const button = document.createElement("button"); button.type = "button"; button.className = "search-results-page-btn"; button.textContent = label; if (options.width === "wide") { button.style.minWidth = "3.9375rem"; } if (options.disabled) { button.disabled = true; button.classList.add("is-disabled"); } if (options.active) { button.classList.add("is-active"); button.setAttribute("aria-current", "page"); } if (typeof options.page === "number" && !options.disabled) { button.dataset.page = String(options.page); } return button; } function getVisiblePages(currentPage, pageCount) { const maxVisible = 6; if (pageCount <= maxVisible) { return Array.from({ length: pageCount }, (_, index) => index + 1); } let start = Math.max(1, currentPage - Math.floor(maxVisible / 2)); let end = start + maxVisible - 1; if (end > pageCount) { end = pageCount; start = end - maxVisible + 1; } return Array.from({ length: end - start + 1 }, (_, index) => start + index); } function renderEmptyState() { const list = document.querySelector("[data-results-list]"); const emptyState = document.querySelector("[data-empty-state]"); const pagination = document.querySelector("[data-pagination]"); if (list) { list.innerHTML = ""; } if (emptyState) { emptyState.hidden = false; } if (pagination) { pagination.hidden = true; pagination.innerHTML = ""; } } function renderResults(keyword, pageData) { const list = document.querySelector("[data-results-list]"); const emptyState = document.querySelector("[data-empty-state]"); const pagination = document.querySelector("[data-pagination]"); if (!list || !emptyState || !pagination) { return; } const infos = Array.isArray(pageData.infos) ? pageData.infos : []; const total = Number(pageData.total) || 0; const pageCount = Math.max(1, Math.ceil(total / API_PAGE_SIZE)); const currentPage = Math.max(1, Math.min(Number(pageData.page) || 1, pageCount)); if (!infos.length) { renderEmptyState(); return; } emptyState.hidden = true; list.innerHTML = infos.map((item) => { const title = item.title || ""; const summary = item.desc || ""; const date = item.publish_at || ""; const url = item._url || item.link || item.url || "./news-detail.html"; return ` ${highlightText(title, keyword)} ${highlightText(summary, keyword)} ${escapeHtml(date)} `; }).join(""); pagination.hidden = pageCount <= 1; pagination.innerHTML = ""; if (pageCount <= 1) { return; } const controls = [ createPaginationButton("首页", { page: 1, width: "wide", disabled: currentPage === 1 }), createPaginationButton("上一页", { page: currentPage - 1, width: "wide", disabled: currentPage === 1 }), ]; const visiblePages = getVisiblePages(currentPage, pageCount); for (const index of visiblePages) { controls.push(createPaginationButton(String(index), { page: index, active: index === currentPage })); } controls.push( createPaginationButton("下一页", { page: currentPage + 1, width: "wide", disabled: currentPage === pageCount }), createPaginationButton("末页", { page: pageCount, width: "wide", disabled: currentPage === pageCount }), ); controls.forEach((button) => { pagination.appendChild(button); }); } function requestSearch(keyword, page) { return new Promise((resolve, reject) => { const callbackName = `codexSearchJsonp_${Date.now()}_${Math.random().toString(16).slice(2)}`; const script = document.createElement("script"); const timer = window.setTimeout(() => { cleanup(); reject(new Error("Search request timed out")); }, 10000); function cleanup() { window.clearTimeout(timer); if (script.parentNode) { script.parentNode.removeChild(script); } try { delete window[callbackName]; } catch (error) { window[callbackName] = undefined; } } window[callbackName] = (payload) => { cleanup(); resolve(payload); }; script.onerror = () => { cleanup(); reject(new Error("Search request failed")); }; const url = new URL(API_ENDPOINT); url.searchParams.set("_orgid_", API_ORG_ID); url.searchParams.set("keyword", keyword); url.searchParams.set("page", String(page)); url.searchParams.set("cqcallback", callbackName); url.searchParams.set("_", String(Date.now())); script.src = url.toString(); document.body.appendChild(script); }); } async function render() { const input = document.querySelector("[data-search-input]"); if (!input) { return; } const { keyword, page } = getSearchState(); const requestToken = ++activeRequestToken; input.value = keyword; if (!keyword) { renderEmptyState(); return; } setLoadingState(true); try { const response = await requestSearch(keyword, page); if (requestToken !== activeRequestToken) { return; } if (!response || Number(response.code) !== 1 || !response.data) { renderEmptyState(); return; } renderResults(keyword, { infos: response.data.infos || [], total: response.data.total || 0, page, }); } catch (error) { if (requestToken === activeRequestToken) { renderEmptyState(); } } finally { if (requestToken === activeRequestToken) { setLoadingState(false); } } } function bindEvents() { const form = document.querySelector("[data-search-form]"); const pagination = document.querySelector("[data-pagination]"); if (!form || !pagination) { return; } form.addEventListener("submit", (event) => { event.preventDefault(); const input = form.querySelector("[data-search-input]"); const keyword = input ? input.value.trim() : ""; const nextUrl = buildUrl(keyword, 1); window.history.replaceState({}, "", nextUrl); render(); }); pagination.addEventListener("click", (event) => { const trigger = event.target.closest("[data-page]"); if (!trigger) { return; } const { keyword } = getSearchState(); const page = Number.parseInt(trigger.dataset.page || "1", 10) || 1; const nextUrl = buildUrl(keyword, page); window.history.replaceState({}, "", nextUrl); render(); // window.scrollTo({ top: 0, behavior: "smooth" }); }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { bindEvents(); render(); }, { once: true }); } else { bindEvents(); render(); } })();
${highlightText(summary, keyword)}