Present completion research as web document
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,296 @@
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
const evidenceData = window.K1412_EVIDENCE;
|
||||
const evidenceList = document.querySelector("#evidence-list");
|
||||
const evidenceCount = document.querySelector("#evidence-count");
|
||||
const emptyEvidence = document.querySelector("#empty-evidence");
|
||||
const searchInput = document.querySelector("#evidence-search");
|
||||
const expandButton = document.querySelector("#expand-evidence");
|
||||
const depthButtons = [...document.querySelectorAll("[data-depth]")];
|
||||
const themeButtons = [...document.querySelectorAll("[data-theme]")];
|
||||
|
||||
const depthLabels = {
|
||||
core: "核心证据",
|
||||
support: "旁证",
|
||||
context: "历史背景",
|
||||
};
|
||||
|
||||
const themeLabels = {
|
||||
verification: "完成证明",
|
||||
protocol: "协议审计",
|
||||
recovery: "失败恢复",
|
||||
reliability: "风险可靠性",
|
||||
context: "历史背景",
|
||||
};
|
||||
|
||||
const state = {
|
||||
query: "",
|
||||
depth: "all",
|
||||
theme: "all",
|
||||
expanded: false,
|
||||
};
|
||||
|
||||
function normalize(value) {
|
||||
return String(value || "")
|
||||
.toLocaleLowerCase("zh-CN")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function searchableText(record) {
|
||||
return normalize(
|
||||
[
|
||||
record.id,
|
||||
record.title,
|
||||
record.depth,
|
||||
record.theme,
|
||||
record.evidence,
|
||||
record.supports,
|
||||
record.boundary,
|
||||
].join(" ")
|
||||
);
|
||||
}
|
||||
|
||||
function filteredRecords() {
|
||||
if (!evidenceData || !Array.isArray(evidenceData.records)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const terms = normalize(state.query).split(" ").filter(Boolean);
|
||||
return evidenceData.records.filter((record) => {
|
||||
if (state.depth !== "all" && record.depth !== state.depth) {
|
||||
return false;
|
||||
}
|
||||
if (state.theme !== "all" && record.theme !== state.theme) {
|
||||
return false;
|
||||
}
|
||||
const haystack = searchableText(record);
|
||||
return terms.every((term) => haystack.includes(term));
|
||||
});
|
||||
}
|
||||
|
||||
function tag(label, className) {
|
||||
const element = document.createElement("span");
|
||||
element.className = `evidence-tag ${className || ""}`.trim();
|
||||
element.textContent = label;
|
||||
return element;
|
||||
}
|
||||
|
||||
function detailColumn(title, text, className = "") {
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = className;
|
||||
|
||||
const heading = document.createElement("h4");
|
||||
heading.textContent = title;
|
||||
const paragraph = document.createElement("p");
|
||||
paragraph.textContent = text || "本轮未将该论文用于直接论证。";
|
||||
|
||||
wrapper.append(heading, paragraph);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function evidenceItem(record) {
|
||||
const details = document.createElement("details");
|
||||
details.className = "evidence-item";
|
||||
details.dataset.depth = record.depth;
|
||||
details.open = state.expanded;
|
||||
|
||||
const summary = document.createElement("summary");
|
||||
|
||||
const id = document.createElement("span");
|
||||
id.className = "evidence-id";
|
||||
id.textContent = record.id;
|
||||
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "evidence-heading";
|
||||
const title = document.createElement("h3");
|
||||
title.textContent = record.title;
|
||||
const tags = document.createElement("div");
|
||||
tags.className = "evidence-tags";
|
||||
tags.append(
|
||||
tag(depthLabels[record.depth] || record.depth, record.depth),
|
||||
tag(themeLabels[record.theme] || record.theme)
|
||||
);
|
||||
heading.append(title, tags);
|
||||
|
||||
const openLabel = document.createElement("span");
|
||||
openLabel.className = "evidence-open";
|
||||
openLabel.textContent = details.open ? "收起" : "展开";
|
||||
|
||||
summary.append(id, heading, openLabel);
|
||||
details.append(summary);
|
||||
|
||||
if (record.depth === "context") {
|
||||
const detail = document.createElement("div");
|
||||
detail.className = "evidence-detail context-detail";
|
||||
detail.append(
|
||||
detailColumn("本轮位置", record.boundary, "evidence-boundary")
|
||||
);
|
||||
details.append(detail);
|
||||
} else {
|
||||
const detail = document.createElement("div");
|
||||
detail.className = "evidence-detail";
|
||||
detail.append(
|
||||
detailColumn("核过的直接证据", record.evidence),
|
||||
detailColumn("可支持的判断", record.supports),
|
||||
detailColumn("不能外推", record.boundary, "evidence-boundary")
|
||||
);
|
||||
details.append(detail);
|
||||
}
|
||||
|
||||
const source = document.createElement("div");
|
||||
source.className = "evidence-source";
|
||||
const sourceLink = document.createElement("a");
|
||||
sourceLink.href = record.url;
|
||||
sourceLink.target = "_blank";
|
||||
sourceLink.rel = "noreferrer";
|
||||
sourceLink.textContent = "打开 arXiv 原文";
|
||||
source.append(sourceLink);
|
||||
details.append(source);
|
||||
|
||||
details.addEventListener("toggle", () => {
|
||||
openLabel.textContent = details.open ? "收起" : "展开";
|
||||
});
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
function updateFilterButtons(buttons, value, attribute) {
|
||||
buttons.forEach((button) => {
|
||||
const active = button.dataset[attribute] === value;
|
||||
button.classList.toggle("is-active", active);
|
||||
button.setAttribute("aria-pressed", String(active));
|
||||
});
|
||||
}
|
||||
|
||||
function renderEvidence() {
|
||||
if (!evidenceData) {
|
||||
evidenceCount.textContent = "证据数据未载入。";
|
||||
emptyEvidence.hidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const records = filteredRecords();
|
||||
const fragment = document.createDocumentFragment();
|
||||
records.forEach((record) => fragment.append(evidenceItem(record)));
|
||||
evidenceList.replaceChildren(fragment);
|
||||
|
||||
const counts = evidenceData.counts;
|
||||
evidenceCount.textContent =
|
||||
`显示 ${records.length} / ${counts.pool} 篇` +
|
||||
` · 核心 ${counts.core} · 旁证 ${counts.support} · 背景 ${counts.context}`;
|
||||
emptyEvidence.hidden = records.length !== 0;
|
||||
expandButton.disabled = records.length === 0;
|
||||
expandButton.textContent = state.expanded ? "收起当前结果" : "展开当前结果";
|
||||
}
|
||||
|
||||
depthButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
state.depth = button.dataset.depth;
|
||||
state.expanded = false;
|
||||
updateFilterButtons(depthButtons, state.depth, "depth");
|
||||
renderEvidence();
|
||||
});
|
||||
});
|
||||
|
||||
themeButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
state.theme = button.dataset.theme;
|
||||
state.expanded = false;
|
||||
updateFilterButtons(themeButtons, state.theme, "theme");
|
||||
renderEvidence();
|
||||
});
|
||||
});
|
||||
|
||||
searchInput?.addEventListener("input", (event) => {
|
||||
state.query = event.target.value;
|
||||
state.expanded = false;
|
||||
renderEvidence();
|
||||
});
|
||||
|
||||
expandButton?.addEventListener("click", () => {
|
||||
state.expanded = !state.expanded;
|
||||
document.querySelectorAll(".evidence-item").forEach((item) => {
|
||||
item.open = state.expanded;
|
||||
const label = item.querySelector(".evidence-open");
|
||||
if (label) {
|
||||
label.textContent = state.expanded ? "收起" : "展开";
|
||||
}
|
||||
});
|
||||
expandButton.textContent = state.expanded ? "收起当前结果" : "展开当前结果";
|
||||
});
|
||||
|
||||
function updateReadingProgress() {
|
||||
const documentHeight =
|
||||
document.documentElement.scrollHeight - window.innerHeight;
|
||||
const progress = documentHeight > 0 ? window.scrollY / documentHeight : 0;
|
||||
const bar = document.querySelector("#reading-progress");
|
||||
if (bar) {
|
||||
bar.style.width = `${Math.min(1, Math.max(0, progress)) * 100}%`;
|
||||
}
|
||||
}
|
||||
|
||||
let progressFrame = 0;
|
||||
window.addEventListener(
|
||||
"scroll",
|
||||
() => {
|
||||
if (progressFrame) {
|
||||
return;
|
||||
}
|
||||
progressFrame = window.requestAnimationFrame(() => {
|
||||
updateReadingProgress();
|
||||
progressFrame = 0;
|
||||
});
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
updateReadingProgress();
|
||||
|
||||
const navAliases = {
|
||||
conclusions: "overview",
|
||||
synthesis: "model",
|
||||
};
|
||||
const navLinks = [...document.querySelectorAll(".primary-nav a")];
|
||||
const sections = [...document.querySelectorAll("[data-section]")];
|
||||
|
||||
const sectionObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const visible = entries
|
||||
.filter((entry) => entry.isIntersecting)
|
||||
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
const targetId = navAliases[visible.target.id] || visible.target.id;
|
||||
navLinks.forEach((link) => {
|
||||
const active = link.getAttribute("href") === `#${targetId}`;
|
||||
link.classList.toggle("is-active", active);
|
||||
if (active) {
|
||||
const nav = link.closest(".primary-nav");
|
||||
if (nav && nav.scrollWidth > nav.clientWidth) {
|
||||
nav.scrollTo({
|
||||
left: Math.max(0, link.offsetLeft - nav.clientWidth / 2),
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
rootMargin: "-18% 0px -62% 0px",
|
||||
threshold: [0, 0.08, 0.2],
|
||||
}
|
||||
);
|
||||
sections.forEach((section) => sectionObserver.observe(section));
|
||||
|
||||
renderEvidence();
|
||||
|
||||
if (window.location.hash) {
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
document.querySelector(window.location.hash)?.scrollIntoView();
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user