-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresearch-watch.js
More file actions
171 lines (156 loc) · 6.3 KB
/
research-watch.js
File metadata and controls
171 lines (156 loc) · 6.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
const watchCopy = {
zh: {
intro: "根据我的研究方向、代表性论文主题与当前关注领域,每日自动抓取和更新最新相关论文,并按相关度与时间综合排序。",
badge: "按相关度与时间排序",
updatedPrefix: "最近更新:",
loading: "正在准备今日推荐文献...",
empty: "暂时还没有抓取到符合条件的最新文章,稍后会自动更新。",
failed: "今日文献推荐暂时加载失败,请稍后刷新重试。",
read: "阅读全文",
doi: "DOI",
source: "数据来源:OpenAlex,每日自动更新",
untitled: "未命名文章",
unknownJournal: "期刊信息待补充",
unknownAuthors: "作者信息待补充",
noAbstract: "暂无摘要,可点击原文链接查看详细内容。",
scoreLabel: "综合推荐"
},
en: {
intro: "Based on my research interests, publication themes, and ongoing focus areas, the site automatically retrieves newly published papers every day and ranks them by combined relevance and recency.",
badge: "Ranked by relevance and recency",
updatedPrefix: "Last updated: ",
loading: "Preparing today's recommended readings...",
empty: "No matching recent papers are available right now. The feed will update automatically.",
failed: "The daily literature feed is temporarily unavailable. Please refresh later.",
read: "Read article",
doi: "DOI",
source: "Source: OpenAlex, updated daily",
untitled: "Untitled article",
unknownJournal: "Journal information unavailable",
unknownAuthors: "Author information unavailable",
noAbstract: "No abstract available yet. Use the article link to view the full record.",
scoreLabel: "Composite rank"
}
};
const watchTopicLabels = {
global_change_biodiversity: {
zh: "全球变化与生物多样性",
en: "Global change and biodiversity"
},
land_use_climate_interactions: {
zh: "土地利用与气候交互",
en: "Land-use and climate interactions"
},
species_distribution_monitoring: {
zh: "物种分布与监测",
en: "Species distribution and monitoring"
},
extinction_risk_conservation: {
zh: "灭绝风险与保护优先",
en: "Extinction risk and conservation priority"
},
dryland_birds_heatwaves: {
zh: "干旱区鸟类与热浪",
en: "Dryland birds and heatwaves"
},
ecological_modelling_biogeography: {
zh: "生态建模与生物地理",
en: "Ecological modelling and biogeography"
}
};
const watchState = {
data: null,
lang: localStorage.getItem("chenchen-ding-language") || "zh"
};
const watchSelectors = {
intro: () => document.querySelector(".watch-intro"),
badge: () => document.querySelector(".watch-badge"),
updated: () => document.querySelector("[data-watch-updated]"),
grid: () => document.getElementById("watchGrid")
};
const formatDate = (value, lang) => {
if (!value) return "";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return new Intl.DateTimeFormat(lang === "zh" ? "zh-CN" : "en-US", {
year: "numeric",
month: "short",
day: "numeric"
}).format(date);
};
const renderWatchState = (state, messageKey = "loading") => {
const lang = watchState.lang in watchCopy ? watchState.lang : "zh";
const copy = watchCopy[lang];
const intro = watchSelectors.intro();
const badge = watchSelectors.badge();
const updated = watchSelectors.updated();
const grid = watchSelectors.grid();
if (intro) intro.textContent = copy.intro;
if (badge) badge.textContent = copy.badge;
if (updated) {
updated.textContent = watchState.data?.generated_at
? `${copy.updatedPrefix}${formatDate(watchState.data.generated_at, lang)}`
: copy.loading;
}
if (!grid) return;
if (state === "message") {
grid.innerHTML = `
<article class="watch-card watch-card-placeholder">
<p>${copy[messageKey]}</p>
</article>
`;
return;
}
const items = (watchState.data?.items || []).slice(0, 12);
if (!items.length) {
renderWatchState("message", "empty");
return;
}
grid.innerHTML = items.map((item, index) => {
const tags = (item.matched_topic_keys || []).map((key) => {
const label = watchTopicLabels[key]?.[lang] || key;
return `<span class="watch-tag">${label}</span>`;
}).join("");
const abstract = item.abstract || copy.noAbstract;
const title = item.title || copy.untitled;
const journal = item.journal || copy.unknownJournal;
const authors = item.authors_text || copy.unknownAuthors;
const primaryUrl = item.primary_url || item.openalex_url || "#";
const doiUrl = item.doi_url || item.openalex_url || primaryUrl;
return `
<article class="watch-card">
<div class="watch-card-head">
<span class="watch-rank">${index + 1}</span>
<span class="watch-date">${formatDate(item.publication_date, lang)}</span>
</div>
<h3><a href="${primaryUrl}" target="_blank" rel="noopener">${title}</a></h3>
<p class="watch-journal">${journal}</p>
<p class="watch-authors">${authors}</p>
<p class="watch-abstract">${abstract}</p>
<div class="watch-tags">${tags}<span class="watch-tag">${copy.scoreLabel}: ${Number(item.score || 0).toFixed(2)}</span></div>
<div class="watch-card-actions">
<a class="watch-action watch-action-primary" href="${primaryUrl}" target="_blank" rel="noopener">${copy.read}</a>
<a class="watch-action watch-action-secondary" href="${doiUrl}" target="_blank" rel="noopener">${copy.doi}</a>
</div>
</article>
`;
}).join("");
};
const loadResearchWatch = async () => {
try {
const response = await fetch("assets/data/research-watch.json", { cache: "no-store" });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
watchState.data = await response.json();
renderWatchState("ready");
} catch (error) {
console.error("Failed to load research watch data", error);
renderWatchState("message", "failed");
}
};
window.addEventListener("chenchen-language-change", (event) => {
watchState.lang = event.detail?.lang || watchState.lang;
renderWatchState(watchState.data ? "ready" : "message", watchState.data ? undefined : "loading");
});
watchState.lang = localStorage.getItem("chenchen-ding-language") || "zh";
renderWatchState("message", "loading");
loadResearchWatch();