// ==UserScript==
// @name Cool Papers Calendar
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Display a calendar showing paper reading progress on papers.cool/arxiv
// @author WeiHongliang
// @match https://papers.cool/arxiv/cs.CL,cs.LG,cs.AI,cs.CV*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
// 导航到特定进度
function navigateToProgress(input) {
console.log(`尝试导航到进度: ${input}`);
// 获取总论文数
const totalPapers = getTotalPapersCount();
let targetPaperIndex = -1;
// 检查输入是百分比还是论文序号
if (input.includes('%')) {
// 百分比输入
const percent = parseInt(input.replace('%', ''));
if (!isNaN(percent) && percent >= 0 && percent <= 100) {
targetPaperIndex = Math.ceil(totalPapers * percent / 100);
if (targetPaperIndex > 0) targetPaperIndex -= 1; // 转为0基索引
console.log(`百分比 ${percent}% 对应论文索引: ${targetPaperIndex + 1}/${totalPapers}`);
}
} else {
// 直接输入论文序号
const paperNumber = parseInt(input);
if (!isNaN(paperNumber) && paperNumber > 0 && paperNumber <= totalPapers) {
targetPaperIndex = paperNumber - 1; // 转为0基索引
console.log(`直接导航到论文: ${paperNumber}/${totalPapers}`);
}
}
if (targetPaperIndex >= 0) {
scrollToTargetPaper(targetPaperIndex + 1); // 转回1基索引进行导航
return true; // 导航成功
} else {
if (input !== '0') { // 忽略导航到第0篇的警告
alert(`请输入有效的进度值(1-${totalPapers}或0%-100%)`);
}
return false; // 导航失败
}
}
// 滚动到目标论文位置
function scrollToTargetPaper(targetNumber) {
// 首先尝试查找目标论文元素
let targetPaper = null;
let allVisible = false;
// 检查目标论文是否已加载
function findTargetPaper() {
const papers = document.querySelectorAll('.panel.paper');
console.log(`当前已加载 ${papers.length} 篇论文`);
for (const paper of papers) {
const titleLink = paper.querySelector('a[title]');
if (titleLink) {
const titleAttr = titleLink.getAttribute('title');
const match = titleAttr?.match(/(\d+)\/\d+/);
if (match && parseInt(match[1]) === targetNumber) {
targetPaper = paper;
console.log(`找到目标论文 #${targetNumber}:`, paper);
return true;
}
}
// 尝试通过索引元素找到目标
const indexEl = paper.querySelector('.index');
if (indexEl && indexEl.textContent.includes(`#${targetNumber}`)) {
targetPaper = paper;
console.log(`通过索引找到目标论文 #${targetNumber}:`, paper);
return true;
}
}
// 检查是否所有论文都已加载
const lastPaper = papers[papers.length - 1];
if (lastPaper) {
const titleLink = lastPaper.querySelector('a[title]');
if (titleLink) {
const titleAttr = titleLink.getAttribute('title');
const match = titleAttr?.match(/(\d+)\/(\d+)/);
if (match && parseInt(match[1]) === parseInt(match[2])) {
allVisible = true;
console.log('所有论文已加载完毕');
return false;
}
}
}
return false;
}
// 尝试直接查找
if (findTargetPaper()) {
// 找到目标,滚动到位置
targetPaper.scrollIntoView({ behavior: 'smooth', block: 'center' });
highlightPaper(targetPaper);
return;
}
// 如果没有找到,需要滚动加载更多
function loadMoreAndFind() {
if (allVisible) {
alert(`无法找到论文 #${targetNumber},请检查输入值是否正确。`);
return;
}
if (findTargetPaper()) {
// 找到目标,滚动到位置
targetPaper.scrollIntoView({ behavior: 'smooth', block: 'center' });
highlightPaper(targetPaper);
return;
}
// 滚动到页面底部以加载更多论文
window.scrollTo(0, document.body.scrollHeight);
// 等待新内容加载后再次尝试
setTimeout(loadMoreAndFind, 800);
}
showTemporaryMessage(`正在定位论文 #${targetNumber}...`);
loadMoreAndFind();
}
// 高亮显示目标论文
function highlightPaper(paperElement) {
// 保存原始样式
const originalBackground = paperElement.style.backgroundColor;
const originalTransition = paperElement.style.transition;
// 应用高亮样式
paperElement.style.transition = 'background-color 1s';
paperElement.style.backgroundColor = '#ffffd0';
// 添加目标标记
const targetMarker = document.createElement('div');
targetMarker.textContent = '→';
targetMarker.style.position = 'absolute';
targetMarker.style.left = '-20px';
targetMarker.style.top = '50%';
targetMarker.style.transform = 'translateY(-50%)';
targetMarker.style.fontSize = '20px';
targetMarker.style.color = '#ff4500';
targetMarker.style.fontWeight = 'bold';
// 确保论文元素有相对定位
if (window.getComputedStyle(paperElement).position === 'static') {
paperElement.style.position = 'relative';
}
paperElement.appendChild(targetMarker);
// 2秒后恢复原样
setTimeout(() => {
paperElement.style.backgroundColor = originalBackground;
// 5秒后移除标记
setTimeout(() => {
if (paperElement.contains(targetMarker)) {
paperElement.removeChild(targetMarker);
}
}, 3000);
}, 2000);
// 记录当前位置
savePaperClick(parseInt(paperElement.querySelector('a[title]')?.getAttribute('title')?.match(/(\d+)\/\d+/)?.[1] || '1') - 1, getTotalPapersCount());
} // 在页面加载时添加的全局函数
// 从URL中获取当前日期或使用当前日期
function getCurrentDateFromUrl() {
// 尝试直接从URL中查找date参数
const urlParams = new URLSearchParams(window.location.search);
const dateParam = urlParams.get('date');
if (dateParam) {
return dateParam;
}
// 如果URL中没有date参数,尝试从页面内容中提取日期
const dateEl = document.querySelector('.date');
if (dateEl && dateEl.textContent) {
// 检查文本内容是否符合日期格式YYYY-MM-DD
const dateMatch = dateEl.textContent.match(/\d{4}-\d{2}-\d{2}/);
if (dateMatch) {
return dateMatch[0];
}
}
// 如果无法从URL或页面内容中提取日期,检查URL本身是否包含日期格式
const urlDateMatch = window.location.href.match(/\d{4}-\d{2}-\d{2}/);
if (urlDateMatch) {
return urlDateMatch[0];
}
// 如果以上方法都失败,使用当前日期
return formatDate(new Date());
}
// 格式化日期为YYYY-MM-DD格式
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 获取日期的进度信息
function getProgressForDate(dateStr) {
const progressData = GM_getValue('paperProgress', {});
const dateProgress = progressData[dateStr];
// 如果有进度数据并且是新格式,返回详细信息
if (dateProgress && typeof dateProgress === 'object') {
return dateProgress;
}
// 如果是旧格式(仅百分比)
else if (dateProgress) {
return {
percent: dateProgress,
current: 0,
total: 0,
lastUpdated: null
};
}
// 如果没有进度数据
else {
return {
percent: 0,
current: 0,
total: 0,
lastUpdated: null
};
}
}
// 从页面获取总论文数量
function getTotalPapersCount() {
// 首先尝试从页面中查找"Total: XXX"格式的文本
const infoText = document.querySelector('.info')?.textContent || '';
const totalMatch = infoText.match(/Total:\s*(\d+)/);
if (totalMatch && totalMatch[1]) {
return parseInt(totalMatch[1], 10);
}
// 如果找不到,尝试使用论文元素数量
const papers = document.querySelectorAll('.panel.paper, .arxiv-result, div.paper, article, .paper-item');
if (papers.length > 0) {
// 查找具有索引值的第一篇论文,提取总数
const firstPaperTitleLink = papers[0].querySelector('a[title]');
if (firstPaperTitleLink) {
const titleAttr = firstPaperTitleLink.getAttribute('title');
const titleMatch = titleAttr?.match(/(\d+)\/(\d+)/);
if (titleMatch && titleMatch[2]) {
return parseInt(titleMatch[2], 10);
}
}
return papers.length;
}
// 默认返回值
return 100;
}
// 标记当前日期为已完成
function markAsComplete() {
// 获取当前日期
const dateStr = getCurrentDateFromUrl();
// 获取总论文数量
const totalPapers = getTotalPapersCount();
console.log(`手动标记 ${dateStr} 为已完成,总论文数: ${totalPapers}`);
// 保存完成状态
const progressData = GM_getValue('paperProgress', {});
progressData[dateStr] = {
percent: 100,
current: totalPapers,
total: totalPapers,
lastUpdated: new Date().toISOString(),
manuallyCompleted: true
};
GM_setValue('paperProgress', progressData);
// 立即更新当前日期的显示
updateCurrentDateDisplay(dateStr);
// 显示简短的成功提示,2秒后自动消失
showTemporaryMessage(`已标记 ${dateStr} 为已完成!`);
}
// 显示临时消息提示
function showTemporaryMessage(message) {
// 检查是否已存在消息框,如果有则移除
const existingMsg = document.getElementById('temp-message');
if (existingMsg) {
document.body.removeChild(existingMsg);
}
// 创建消息框
const msgBox = document.createElement('div');
msgBox.id = 'temp-message';
msgBox.style.position = 'fixed';
msgBox.style.bottom = '20px';
msgBox.style.left = '50%';
msgBox.style.transform = 'translateX(-50%)';
msgBox.style.backgroundColor = '#4caf50';
msgBox.style.color = 'white';
msgBox.style.padding = '10px 20px';
msgBox.style.borderRadius = '4px';
msgBox.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
msgBox.style.zIndex = '10000';
msgBox.style.fontWeight = 'bold';
msgBox.style.fontSize = '14px';
msgBox.style.textAlign = 'center';
msgBox.textContent = message;
// 添加到页面
document.body.appendChild(msgBox);
// 2秒后自动移除
setTimeout(() => {
if (msgBox.parentNode) {
document.body.removeChild(msgBox);
}
}, 2000);
}
// 立即更新当前日期的显示
function updateCurrentDateDisplay(dateStr) {
// 获取当前显示的年月
const headerText = document.querySelector('.calendar-header div').textContent;
const monthNames = ["一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"];
const monthName = headerText.split(' ')[0];
const year = parseInt(headerText.split(' ')[1]);
const month = monthNames.indexOf(monthName);
// 解析日期字符串
const dateParts = dateStr.split('-');
const targetYear = parseInt(dateParts[0]);
const targetMonth = parseInt(dateParts[1]) - 1; // 月份从0开始
const targetDay = parseInt(dateParts[2]);
// 检查当前日期是否在显示的月份中
if (targetYear === year && targetMonth === month) {
console.log(`更新日历中 ${year}年${month+1}月${targetDay}日 的显示`);
// 查找日期对应的单元格
const dayCells = document.querySelectorAll('.calendar-day');
dayCells.forEach(cell => {
const dayNumber = cell.querySelector('.day-number');
if (dayNumber && parseInt(dayNumber.textContent) === targetDay) {
// 清除旧的内容
while (cell.childNodes.length > 1) { // 保留日期数字元素
if (cell.childNodes[1] !== dayNumber) {
cell.removeChild(cell.childNodes[1]);
} else {
if (cell.childNodes[2]) {
cell.removeChild(cell.childNodes[2]);
}
}
}
// 移除所有类
cell.classList.remove('no-progress', 'partial-progress', 'complete-progress');
// 设置为完成状态
cell.classList.add('complete-progress');
// 更新标题提示
const totalPapers = getTotalPapersCount();
cell.title = `已完成: 100%(${totalPapers}篇论文)(手动标记)`;
// 添加百分比显示
const percentDiv = document.createElement('div');
percentDiv.className = 'progress-percent';
percentDiv.textContent = '100%';
cell.appendChild(percentDiv);
// 添加总数信息
const totalDiv = document.createElement('div');
totalDiv.className = 'progress-total';
totalDiv.textContent = `${totalPapers}/${totalPapers}`;
cell.appendChild(totalDiv);
// 添加手动完成的✓标记
const checkmarkDiv = document.createElement('div');
checkmarkDiv.style.position = 'absolute';
checkmarkDiv.style.top = '2px';
checkmarkDiv.style.right = '2px';
checkmarkDiv.style.fontSize = '10px';
checkmarkDiv.style.color = '#fff';
checkmarkDiv.style.fontWeight = 'bold';
checkmarkDiv.textContent = '✓';
cell.appendChild(checkmarkDiv);
console.log('已更新日历单元格显示:', cell);
return;
}
});
} else {
// 如果当前日期不在显示的月份中,更新整个日历
console.log(`当前日期 ${dateStr} 不在显示的月份中,更新整个日历UI`);
updateCalendarUI();
}
}
(function() {
'use strict';
// 为日历添加样式
GM_addStyle(`
#progress-calendar {
position: fixed;
top: 10px;
right: 50px; /* 将日历从右边缘移开一些距离 */
background: white;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
padding-bottom: 30px; /* 底部增加padding,为右下角的关闭按钮留出空间 */
z-index: 9999;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
font-family: Arial, sans-serif;
max-width: 350px;
/* 防止翻译影响 */
translate: none !important;
transform: none !important;
font-style: normal !important;
font-weight: normal !important;
text-align: left !important;
}
#temp-message {
animation: fadeInOut 2s ease-in-out;
/* 防止翻译影响 */
translate: none !important;
transform: translateX(-50%) !important;
font-style: normal !important;
}
@keyframes fadeInOut {
0% { opacity: 0; transform: translate(-50%, 20px); }
10% { opacity: 1; transform: translate(-50%, 0); }
90% { opacity: 1; transform: translate(-50%, 0); }
100% { opacity: 0; transform: translate(-50%, 20px); }
}
/* 确保所有日历元素不受翻译影响 */
.calendar-header, .calendar-grid, .calendar-day-header, .calendar-day,
.day-number, .progress-percent, .progress-total,
.complete-button, .progress-navigator, .progress-input, .nav-button {
translate: none !important;
transform: none !important;
font-style: normal !important;
text-transform: none !important;
}
/* 修复翻译后可能的布局问题 */
.calendar-grid {
display: grid !important;
grid-template-columns: repeat(7, 1fr) !important;
}
.calendar-day {
width: 40px !important;
height: 40px !important;
}
.calendar-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
align-items: center;
}
.calendar-header button {
border: none;
background: #f0f0f0;
border-radius: 3px;
padding: 2px 8px;
cursor: pointer;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
}
.calendar-day {
width: 40px;
height: 40px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 5px;
cursor: pointer;
font-size: 11px;
position: relative;
overflow: hidden;
}
.day-number {
font-weight: bold;
position: absolute;
top: 2px;
left: 4px;
font-size: 10px;
}
.progress-percent {
font-size: 10px;
font-weight: bold;
}
.progress-total {
font-size: 8px;
opacity: 0.8;
}
.progress-navigator {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
text-align: center;
}
.progress-input {
width: 60px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
margin-right: 5px;
}
.nav-button {
padding: 5px 10px;
background-color: #2196f3;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.nav-button:hover {
background-color: #0b7dda;
}
.calendar-day-header {
font-weight: bold;
text-align: center;
font-size: 12px;
}
.no-progress {
background-color: #f0f0f0;
}
.partial-progress {
background-color: #ffeb3b;
}
.complete-progress {
background-color: #4caf50;
color: white;
}
.current-day {
border: 2px solid #2196f3;
}
.progress-info {
font-size: 12px;
margin-top: 10px;
text-align: center;
}
.toggle-calendar {
position: fixed;
top: 10px;
right: 10px;
z-index: 9998;
background: #2196f3;
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
`);
// 当DOM加载完成时初始化
window.addEventListener('load', initialize);
function initialize() {
// 检查是否在相关页面上
if (window.location.href.includes('papers.cool/arxiv')) {
console.log('初始化论文阅读进度日历插件...');
// 创建展开/折叠按钮
createToggleButton();
// 创建日历
createCalendar();
// 添加论文链接的点击监听器
addPaperClickListeners();
// 记录初始状态
const totalPapers = getTotalPapersCount();
console.log(`检测到总论文数: ${totalPapers}`);
console.log('当前URL:', window.location.href);
console.log('当前日期:', getCurrentDateFromUrl());
// 检查是否需要滚动到上次的阅读进度
checkAndScrollToLastProgress();
// 定期重新初始化点击监听器,以捕获动态加载的内容
setInterval(() => {
console.log('重新初始化点击监听器...');
addPaperClickListeners();
}, 60000); // 每分钟检查一次
}
}
// 检查并滚动到上次的阅读进度
function checkAndScrollToLastProgress() {
const lastNavigation = GM_getValue('last_navigation', null);
if (!lastNavigation) return;
// 检查是否是最近导航(30秒内)且标记了需要滚动到进度位置
const currentTime = new Date().getTime();
const isRecent = (currentTime - lastNavigation.timestamp) < 30000; // 30秒内
if (isRecent && lastNavigation.shouldScrollToProgress && lastNavigation.progress && lastNavigation.progress.current > 0) {
console.log(`检测到最近导航记录,将滚动到进度: ${lastNavigation.progress.current}/${lastNavigation.progress.total}`);
// 延迟一些时间等待页面完全加载
setTimeout(() => {
// 滚动到进度位置
navigateToProgress(lastNavigation.progress.current.toString());
// 清除导航记录,避免重复滚动
GM_setValue('last_navigation', null);
console.log('已清除导航记录');
}, 1500); // 延迟1.5秒
} else {
// 清除过期的导航记录
if (!isRecent) {
GM_setValue('last_navigation', null);
console.log('清除过期的导航记录');
}
}
}
function createToggleButton() {
const toggleButton = document.createElement('button');
toggleButton.className = 'toggle-calendar';
toggleButton.textContent = 'C';
toggleButton.title = '显示/隐藏阅读进度日历';
toggleButton.addEventListener('click', () => {
const calendar = document.getElementById('progress-calendar');
if (calendar.style.display === 'none') {
calendar.style.display = 'block';
toggleButton.style.display = 'none';
} else {
calendar.style.display = 'none';
}
});
document.body.appendChild(toggleButton);
}
function createCalendar() {
const calendarDiv = document.createElement('div');
calendarDiv.id = 'progress-calendar';
calendarDiv.className = 'notranslate'; // 防止翻译
calendarDiv.setAttribute('translate', 'no'); // 防止翻译
// 获取页面的日期,如果URL中有日期,则使用该日期;否则使用当前日期
const urlDateStr = getCurrentDateFromUrl();
let calendarDate;
if (urlDateStr) {
// 解析URL中的日期
const dateParts = urlDateStr.split('-');
if (dateParts.length === 3) {
const year = parseInt(dateParts[0]);
const month = parseInt(dateParts[1]) - 1; // 月份从0开始
const day = parseInt(dateParts[2]);
calendarDate = new Date(year, month, day);
console.log(`使用URL中的日期初始化日历: ${urlDateStr}`);
} else {
calendarDate = new Date();
}
} else {
calendarDate = new Date();
}
const year = calendarDate.getFullYear();
const month = calendarDate.getMonth();
// 日历头部
const header = document.createElement('div');
header.className = 'calendar-header notranslate';
header.setAttribute('translate', 'no');
const monthNames = ["一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"];
header.innerHTML = `
<button id="prev-month" class="notranslate" translate="no"><</button>
<div class="notranslate" translate="no">${monthNames[month]} ${year}</div>
<button id="next-month" class="notranslate" translate="no">></button>
`;
calendarDiv.appendChild(header);
// 添加星期头部
const dayGrid = document.createElement('div');
dayGrid.className = 'calendar-grid notranslate';
dayGrid.setAttribute('translate', 'no');
const dayNames = ["日", "一", "二", "三", "四", "五", "六"];
dayNames.forEach(day => {
const dayHeader = document.createElement('div');
dayHeader.className = 'calendar-day-header notranslate';
dayHeader.setAttribute('translate', 'no');
dayHeader.textContent = day;
dayGrid.appendChild(dayHeader);
});
// 计算月份第一天和总天数
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
// 为月份第一天前的日子添加空单元格
for (let i = 0; i < firstDay; i++) {
const emptyDay = document.createElement('div');
emptyDay.className = 'notranslate';
emptyDay.setAttribute('translate', 'no');
dayGrid.appendChild(emptyDay);
}
// 添加带有进度指示器的日子
for (let day = 1; day <= daysInMonth; day++) {
const dayCell = document.createElement('div');
dayCell.className = 'calendar-day notranslate';
dayCell.setAttribute('translate', 'no');
// 添加日期数字
const dayNumber = document.createElement('div');
dayNumber.className = 'day-number notranslate';
dayNumber.setAttribute('translate', 'no');
dayNumber.textContent = day;
dayCell.appendChild(dayNumber);
// 将日期格式化为YYYY-MM-DD
const dateStr = formatDate(new Date(year, month, day));
// 获取这个日期的进度
const progress = getProgressForDate(dateStr);
// 添加进度显示
if (progress.percent > 0) {
// 添加进度百分比
const percentDiv = document.createElement('div');
percentDiv.className = 'progress-percent notranslate';
percentDiv.setAttribute('translate', 'no');
percentDiv.textContent = `${progress.percent}%`;
dayCell.appendChild(percentDiv);
// 添加总数信息
if (progress.total > 0) {
const totalDiv = document.createElement('div');
totalDiv.className = 'progress-total notranslate';
totalDiv.setAttribute('translate', 'no');
totalDiv.textContent = `${progress.current}/${progress.total}`;
dayCell.appendChild(totalDiv);
}
}
// 根据进度应用适当的类
if (progress.percent === 0) {
dayCell.classList.add('no-progress');
} else if (progress.percent < 100) {
dayCell.classList.add('partial-progress');
dayCell.title = `进度: ${progress.percent}%(${progress.current}/${progress.total}篇论文)`;
} else {
dayCell.classList.add('complete-progress');
dayCell.title = `已完成: 100%(${progress.total}篇论文)`;
}
// 标记当前显示的日期(如果与URL中的日期匹配)
if (day === calendarDate.getDate() && month === calendarDate.getMonth() && year === calendarDate.getFullYear()) {
dayCell.classList.add('current-day');
dayCell.title = (dayCell.title ? dayCell.title + ' (当前页面日期)' : '当前页面日期');
}
// 同时标记今天的日期(如果在当月)
const today = new Date();
if (day === today.getDate() && month === today.getMonth() && year === today.getFullYear()) {
// 今天的日期用蓝色边框标出,但不覆盖current-day的样式
if (!dayCell.classList.contains('current-day')) {
dayCell.style.border = '2px solid #2196f3';
dayCell.title = (dayCell.title ? dayCell.title + ' (今天)' : '今天');
}
}
// 添加点击事件以导航到该日期
dayCell.addEventListener('click', () => {
navigateToDate(dateStr);
});
dayGrid.appendChild(dayCell);
}
calendarDiv.appendChild(dayGrid);
// 添加进度信息
const progressInfo = document.createElement('div');
progressInfo.className = 'progress-info notranslate';
progressInfo.setAttribute('translate', 'no');
progressInfo.innerHTML = `
<div class="notranslate" translate="no">灰色: 未阅读</div>
<div class="notranslate" translate="no">黄色: 部分阅读</div>
<div class="notranslate" translate="no">绿色: 已完成</div>
`;
calendarDiv.appendChild(progressInfo);
// 添加完成按钮
const completeButtonContainer = document.createElement('div');
completeButtonContainer.className = 'complete-button-container notranslate';
completeButtonContainer.setAttribute('translate', 'no');
completeButtonContainer.style.textAlign = 'center';
completeButtonContainer.style.marginTop = '10px';
const completeButton = document.createElement('button');
completeButton.id = 'mark-complete-button';
completeButton.textContent = '标记当前日期为已完成';
completeButton.className = 'complete-button notranslate';
completeButton.setAttribute('translate', 'no');
completeButton.style.backgroundColor = '#4caf50';
completeButton.style.color = 'white';
completeButton.style.border = 'none';
completeButton.style.padding = '8px 15px';
completeButton.style.borderRadius = '4px';
completeButton.style.cursor = 'pointer';
completeButton.style.fontWeight = 'bold';
completeButton.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
completeButton.onclick = function() {
console.log('完成按钮被点击');
markAsComplete();
return false;
};
completeButton.addEventListener('mouseover', () => {
completeButton.style.backgroundColor = '#45a049';
completeButton.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
});
completeButton.addEventListener('mouseout', () => {
completeButton.style.backgroundColor = '#4caf50';
completeButton.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
});
completeButtonContainer.appendChild(completeButton);
calendarDiv.appendChild(completeButtonContainer);
// 添加进度导航
const progressNavigator = document.createElement('div');
progressNavigator.className = 'progress-navigator notranslate';
progressNavigator.setAttribute('translate', 'no');
const navigatorLabel = document.createElement('div');
navigatorLabel.className = 'notranslate';
navigatorLabel.setAttribute('translate', 'no');
navigatorLabel.textContent = '直接导航到进度位置:';
navigatorLabel.style.marginBottom = '5px';
progressNavigator.appendChild(navigatorLabel);
const inputContainer = document.createElement('div');
inputContainer.className = 'notranslate';
inputContainer.setAttribute('translate', 'no');
// 创建输入框 - 可以输入论文号或百分比
const progressInput = document.createElement('input');
progressInput.type = 'text';
progressInput.placeholder = '输入位置';
progressInput.className = 'progress-input notranslate';
progressInput.setAttribute('translate', 'no');
inputContainer.appendChild(progressInput);
// 创建导航按钮
const navButton = document.createElement('button');
navButton.textContent = '跳转';
navButton.className = 'nav-button notranslate';
navButton.setAttribute('translate', 'no');
navButton.id = 'progress-nav-button';
inputContainer.appendChild(navButton);
progressNavigator.appendChild(inputContainer);
// 添加导航提示
const navHint = document.createElement('div');
navHint.className = 'notranslate';
navHint.setAttribute('translate', 'no');
navHint.style.fontSize = '10px';
navHint.style.marginTop = '5px';
navHint.style.color = '#666';
navHint.textContent = '输入数字(如20)或百分比(如20%)';
progressNavigator.appendChild(navHint);
calendarDiv.appendChild(progressNavigator);
// 添加页面日期信息
if (urlDateStr) {
const dateInfo = document.createElement('div');
dateInfo.className = 'date-info notranslate';
dateInfo.setAttribute('translate', 'no');
dateInfo.style.fontSize = '10px';
dateInfo.style.marginTop = '8px';
dateInfo.style.color = '#666';
dateInfo.style.textAlign = 'center';
dateInfo.textContent = `当前页面日期: ${urlDateStr}`;
calendarDiv.appendChild(dateInfo);
}
// 添加关闭按钮
const closeButton = document.createElement('button');
closeButton.textContent = '×';
closeButton.className = 'notranslate calendar-close-btn';
closeButton.setAttribute('translate', 'no');
closeButton.style.position = 'absolute';
closeButton.style.bottom = '5px'; // 放在底部
closeButton.style.right = '5px'; // 放在右侧
closeButton.style.background = '#f44336'; // 红色背景
closeButton.style.color = 'white'; // 白色文字
closeButton.style.border = 'none';
closeButton.style.borderRadius = '50%'; // 圆形按钮
closeButton.style.width = '20px';
closeButton.style.height = '20px';
closeButton.style.cursor = 'pointer';
closeButton.style.fontSize = '14px';
closeButton.style.lineHeight = '16px'; // 调整文字垂直居中
closeButton.style.textAlign = 'center';
closeButton.style.zIndex = '10000'; // 确保在最上层
closeButton.style.display = 'flex';
closeButton.style.justifyContent = 'center';
closeButton.style.alignItems = 'center';
closeButton.style.boxShadow = '0 1px 3px rgba(0,0,0,0.3)';
closeButton.addEventListener('click', () => {
calendarDiv.style.display = 'none';
document.querySelector('.toggle-calendar').style.display = 'flex';
});
// 添加鼠标悬停效果
closeButton.addEventListener('mouseover', () => {
closeButton.style.backgroundColor = '#d32f2f'; // 深红色
});
closeButton.addEventListener('mouseout', () => {
closeButton.style.backgroundColor = '#f44336'; // 恢复原来的红色
});
calendarDiv.appendChild(closeButton);
// 将日历添加到页面
document.body.appendChild(calendarDiv);
// 为导航按钮添加事件监听器
document.getElementById('prev-month').addEventListener('click', () => {
navigateMonth(-1);
});
document.getElementById('next-month').addEventListener('click', () => {
navigateMonth(1);
});
// 确保完成按钮正常工作
document.getElementById('mark-complete-button').addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('完成按钮被点击 (addEventListener)');
markAsComplete();
});
// 为进度导航按钮添加事件监听器
document.getElementById('progress-nav-button').addEventListener('click', function() {
const inputValue = progressInput.value.trim();
if (inputValue) {
navigateToProgress(inputValue);
}
});
// 为进度输入框添加回车键事件
progressInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const inputValue = progressInput.value.trim();
if (inputValue) {
navigateToProgress(inputValue);
}
}
});
}
function navigateMonth(offset) {
// 获取日历头部显示的月份和年份
const headerText = document.querySelector('.calendar-header div').textContent;
const monthNames = ["一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"];
const currentMonthName = headerText.split(' ')[0];
const currentYear = parseInt(headerText.split(' ')[1]);
const currentMonth = monthNames.indexOf(currentMonthName);
// 计算新的月份和年份
let newMonth = currentMonth + offset;
let newYear = currentYear;
if (newMonth < 0) {
newMonth = 11;
newYear--;
} else if (newMonth > 11) {
newMonth = 0;
newYear++;
}
// 更新日历头部显示
const header = document.querySelector('.calendar-header div');
header.textContent = `${monthNames[newMonth]} ${newYear}`;
// 重建日历天数
updateCalendarDays(newYear, newMonth);
// BUGFIX: The following line was causing the issue and has been removed.
// updateCurrentDateDisplay(getCurrentDateFromUrl());
// updateCalendarDays already handles rendering the current page's date correctly
// if it's in the newly displayed month.
}
function updateCalendarDays(year, month) {
const dayGrid = document.querySelector('.calendar-grid');
// 移除所有现有的日期单元格
while (dayGrid.children.length > 7) { // 保留星期头部
dayGrid.removeChild(dayGrid.lastChild);
}
// 计算月份第一天和总天数
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const totalPapers = getTotalPapersCount(); // 获取最新的总论文数
// 获取当前页面的日期
const currentDateStr = getCurrentDateFromUrl(); // 获取当前页面的日期字符串
let currentDate = null;
if(currentDateStr){
const dateParts = currentDateStr.split('-');
currentDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1]) - 1, parseInt(dateParts[2]));
}
// 为月份第一天前的日子添加空单元格
for (let i = 0; i < firstDay; i++) {
const emptyDay = document.createElement('div');
emptyDay.className = 'notranslate';
emptyDay.setAttribute('translate', 'no');
dayGrid.appendChild(emptyDay);
}
// 添加带有进度指示器的日子
for (let day = 1; day <= daysInMonth; day++) {
const dayCell = document.createElement('div');
dayCell.className = 'calendar-day notranslate';
dayCell.setAttribute('translate', 'no');
// 添加日期数字
const dayNumber = document.createElement('div');
dayNumber.className = 'day-number notranslate';
dayNumber.setAttribute('translate', 'no');
dayNumber.textContent = day;
dayCell.appendChild(dayNumber);
// 将日期格式化为YYYY-MM-DD
const dateStr = formatDate(new Date(year, month, day));
// 获取这个日期的进度
const progress = getProgressForDate(dateStr);
// 添加进度显示
if (progress.percent > 0) {
// 添加进度百分比
const percentDiv = document.createElement('div');
percentDiv.className = 'progress-percent notranslate';
percentDiv.setAttribute('translate', 'no');
percentDiv.textContent = `${progress.percent}%`;
dayCell.appendChild(percentDiv);
// 添加总数信息
if (progress.total > 0) {
const totalDiv = document.createElement('div');
totalDiv.className = 'progress-total notranslate';
totalDiv.setAttribute('translate', 'no');
totalDiv.textContent = `${progress.current}/${progress.total}`;
dayCell.appendChild(totalDiv);
}
// 如果是手动完成的,添加一个✓标记
if (progress.manuallyCompleted) {
const checkmarkDiv = document.createElement('div');
checkmarkDiv.style.position = 'absolute';
checkmarkDiv.style.top = '2px';
checkmarkDiv.style.right = '2px';
checkmarkDiv.style.fontSize = '10px';
checkmarkDiv.style.color = '#fff';
checkmarkDiv.style.fontWeight = 'bold';
checkmarkDiv.textContent = '✓';
dayCell.appendChild(checkmarkDiv);
}
}
// 移除现有的进度类
dayCell.classList.remove('no-progress', 'partial-progress', 'complete-progress', 'current-day');
// 根据进度应用适当的类
if (progress.percent === 0) {
dayCell.classList.add('no-progress');
dayCell.title = '';
} else if (progress.percent < 100) {
dayCell.classList.add('partial-progress');
dayCell.title = `进度: ${progress.percent}%(${progress.current}/${progress.total}篇论文)`;
} else {
dayCell.classList.add('complete-progress');
dayCell.title = `已完成: 100%(${progress.total}篇论文)`;
if (progress.manuallyCompleted) {
dayCell.title += '(手动标记)';
}
}
// 标记当前日期
if (currentDate && day === currentDate.getDate() && month === currentDate.getMonth() && year === currentDate.getFullYear()) {
dayCell.classList.add('current-day');
dayCell.title = (dayCell.title ? dayCell.title + ' (当前页面日期)' : '当前页面日期');
}
// 添加点击事件以导航到该日期
dayCell.addEventListener('click', () => {
navigateToDate(dateStr);
});
dayGrid.appendChild(dayCell);
}
}
function navigateToDate(dateStr) {
// 保存跳转前的当前日期,用于对比是否需要滚动到进度位置
const currentDateStr = getCurrentDateFromUrl();
const isChangingDate = (currentDateStr !== dateStr);
// 保存进度信息以便后续使用
const progressData = GM_getValue('paperProgress', {});
const progress = progressData[dateStr] || { percent: 0, current: 0, total: 0 };
// 从URL中提取当前类别或使用默认值
let categories = 'cs.CL,cs.LG,cs.AI,cs.CV';
const match = window.location.pathname.match(/\/arxiv\/([^?]+)/);
if (match && match[1]) {
categories = match[1];
}
// 构建导航URL
const newUrl = `https://papers.cool/arxiv/${categories}?date=${dateStr}&sort=1`;
// 存储跳转信息,用于页面加载后自动滚动
GM_setValue('last_navigation', {
date: dateStr,
timestamp: new Date().getTime(),
progress: progress,
shouldScrollToProgress: isChangingDate && progress.current > 0
});
console.log(`导航到 ${dateStr},进度: ${progress.percent}%(${progress.current}/${progress.total})`);
// 导航到特定日期的URL
window.location.href = newUrl;
}
// 从页面获取总论文数量
function getTotalPapersCount() {
// 首先尝试从页面中查找"Total: XXX"格式的文本
const infoText = document.querySelector('.info')?.textContent || '';
const totalMatch = infoText.match(/Total:\s*(\d+)/);
if (totalMatch && totalMatch[1]) {
return parseInt(totalMatch[1], 10);
}
// 如果找不到,尝试使用论文元素数量
const papers = document.querySelectorAll('.panel.paper, .arxiv-result, div.paper, article, .paper-item');
if (papers.length > 0) {
// 查找具有索引值的第一篇论文,提取总数
const firstPaperTitleLink = papers[0].querySelector('a[title]');
if (firstPaperTitleLink) {
const titleAttr = firstPaperTitleLink.getAttribute('title');
const titleMatch = titleAttr?.match(/(\d+)\/(\d+)/);
if (titleMatch && titleMatch[2]) {
return parseInt(titleMatch[2], 10);
}
}
return papers.length;
}
// 默认返回值
return 100;
}
var _coolPapers_globalPaperClickHandler = null;
// Replace the existing addPaperClickListeners function with this:
function addPaperClickListeners() {
// If a handler from a previous call to this function exists, remove it.
if (_coolPapers_globalPaperClickHandler) {
document.removeEventListener('click', _coolPapers_globalPaperClickHandler);
}
// Define the actual event handling logic.
// This function will be (re)assigned to _coolPapers_globalPaperClickHandler each time addPaperClickListeners is called.
_coolPapers_globalPaperClickHandler = function(e) {
const clickedElement = e.target;
// Find the closest ancestor anchor tag
const linkElement = clickedElement.closest('a');
if (!linkElement) {
return; // Not a click on or within a link
}
let paperId = '';
const linkId = linkElement.id || '';
// Use getAttribute('href') as linkElement.href can be the fully resolved URL
const linkHref = linkElement.getAttribute('href') || '';
const linkClassName = (typeof linkElement.className === 'string') ? linkElement.className : '';
// --- Step 1: Try to extract paperId ---
// Priority 1: Links with IDs like "prefix-PAPERID" (e.g., "pdf-2505.05410", "kimi-2505.05410")
const idPrefixes = ['pdf-', 'kimi-', 'title-', 'copy-', 'rel-'];
for (const prefix of idPrefixes) {
if (linkId.startsWith(prefix)) {
paperId = linkId.substring(prefix.length);
break;
}
}
// Priority 2: Links with href containing a paper ID pattern (e.g., arXiv abstract or PDF links)
if (!paperId && linkHref) {
const hrefMatch = linkHref.match(/(\d{4}\.\d{4,5}(v\d+)?)/); // Matches patterns like 2505.05410 or 1234.56789v2
if (hrefMatch && hrefMatch[1]) {
paperId = hrefMatch[1];
}
}
// --- Step 2: If paperId found, find the paper panel and its index ---
if (paperId) {
const allPaperPanels = document.querySelectorAll('.panel.paper, .arxiv-result, div.paper, article, .paper-item');
let paperPanelElement = null;
let domOrderIndex = -1;
// Find the specific paper panel by its ID (which should be the paperId)
for (let i = 0; i < allPaperPanels.length; i++) {
if (allPaperPanels[i].id === paperId) {
paperPanelElement = allPaperPanels[i];
domOrderIndex = i;
break;
}
}
if (paperPanelElement) {
let officialIndex = -1; // 0-based index
// Try to get the "official" index (e.g., "1/293") from the panel content
// This is typically on the main link to the ArXiv abstract page
const mainAbstractLink = paperPanelElement.querySelector('h2.title a[href*="arxiv.org/abs/"][title]');
if (mainAbstractLink && mainAbstractLink.title) {
const titleMatch = mainAbstractLink.title.match(/^(\d+)\s*\/\s*\d+$/); // Matches "N/M"
if (titleMatch && titleMatch[1]) {
officialIndex = parseInt(titleMatch[1], 10) - 1; // Convert to 0-based
}
}
// Fallback: Try to get index from a child span.index element (e.g. "#1")
if (officialIndex === -1) {
const indexSpan = paperPanelElement.querySelector('span.index');
if (indexSpan && indexSpan.textContent) {
const spanMatch = indexSpan.textContent.match(/#(\d+)/);
if (spanMatch && spanMatch[1]) {
officialIndex = parseInt(spanMatch[1], 10) - 1; // Convert to 0-based
}
}
}
const indexToSave = (officialIndex !== -1) ? officialIndex : domOrderIndex;
if (indexToSave !== -1) {
console.log(`Cool Papers Calendar: Clicked paper ID ${paperId} (link: ${linkId || linkClassName || linkHref}), determined index ${indexToSave + 1}`);
savePaperClick(indexToSave, getTotalPapersCount());
// Optional: Visual feedback on the paper panel
const originalBg = paperPanelElement.style.backgroundColor;
paperPanelElement.style.backgroundColor = '#ffff99'; // Light yellow feedback
setTimeout(() => {
if (paperPanelElement) paperPanelElement.style.backgroundColor = originalBg;
}, 500);
} else {
console.warn(`Cool Papers Calendar: Could not determine a valid index for paper ID ${paperId}.`);
}
return; // Processed this click.
} else {
console.log(`Cool Papers Calendar: Paper panel for ID ${paperId} not found. Link:`, linkElement);
}
}
// --- Step 3: Fallback for links without direct paperId, but inside a paper panel ---
// (e.g., author links)
const containingPanel = linkElement.closest('.panel.paper, .arxiv-result, div.paper, article, .paper-item');
if (containingPanel) {
let panelIndex = -1;
// Try to get index from panel's main abstract link title
const mainAbstractLink = containingPanel.querySelector('h2.title a[href*="arxiv.org/abs/"][title]');
if (mainAbstractLink && mainAbstractLink.title) {
const titleMatch = mainAbstractLink.title.match(/^(\d+)\s*\/\s*\d+$/);
if (titleMatch && titleMatch[1]) {
panelIndex = parseInt(titleMatch[1], 10) - 1;
}
}
// Fallback: try from span.index
if (panelIndex === -1) {
const indexSpan = containingPanel.querySelector('span.index');
if (indexSpan && indexSpan.textContent) {
const spanMatch = indexSpan.textContent.match(/#(\d+)/);
if (spanMatch && spanMatch[1]) {
panelIndex = parseInt(spanMatch[1], 10) - 1;
}
}
}
// Fallback: DOM order of all panels
if (panelIndex === -1) {
const allPanels = document.querySelectorAll('.panel.paper, .arxiv-result, div.paper, article, .paper-item');
panelIndex = Array.from(allPanels).indexOf(containingPanel);
}
if (panelIndex !== -1) {
console.log(`Cool Papers Calendar: Clicked link inside paper panel (DOM index ${panelIndex + 1}). Link:`, linkElement);
savePaperClick(panelIndex, getTotalPapersCount());
// Optional: Visual feedback
const originalBg = containingPanel.style.backgroundColor;
containingPanel.style.backgroundColor = '#ffffcc';
setTimeout(() => {
if (containingPanel) containingPanel.style.backgroundColor = originalBg;
}, 500);
return; // Processed this click.
}
}
// If we reach here, the click was on a link, but we couldn't associate it with a paper progress.
// console.log('Cool Papers Calendar: Clicked link not associated with a paper for progress tracking:', linkElement);
}; // End of _coolPapers_globalPaperClickHandler definition
// Add the newly defined handler to the document
document.addEventListener('click', _coolPapers_globalPaperClickHandler);
console.log('Cool Papers Calendar: Global paper click listener attached/updated.');
}
function savePaperClick(paperIndex, totalPapers) {
// 从URL中提取日期或使用当前日期
let dateStr = getCurrentDateFromUrl();
// 计算进度(位置/总数)
const progress = Math.round(((paperIndex + 1) / totalPapers) * 100);
console.log(`点击了第 ${paperIndex + 1}/${totalPapers} 篇论文,进度 ${progress}%,日期 ${dateStr}`);
// 保存这个日期的详细进度信息
const progressData = GM_getValue('paperProgress', {});
// 检查是否已存在数据,如果没有或者新进度比老进度更高,则更新
const existingData = progressData[dateStr];
let shouldUpdate = false;
if (!existingData) {
shouldUpdate = true;
} else if (existingData.percent < progress) {
shouldUpdate = true;
} else if (existingData.current < paperIndex + 1) {
shouldUpdate = true;
}
if (shouldUpdate) {
progressData[dateStr] = {
percent: progress,
current: paperIndex + 1,
total: totalPapers,
lastUpdated: new Date().toISOString()
};
// 如果是手动完成的,保留该标志
if (existingData && existingData.manuallyCompleted) {
progressData[dateStr].manuallyCompleted = true;
}
GM_setValue('paperProgress', progressData);
// 更新日历UI
updateCalendarUI();
console.log(`✅ 更新了 ${dateStr} 的进度: ${progress}%(${paperIndex + 1}/${totalPapers})`);
} else {
console.log(`❌ 不更新进度,因为当前进度 ${existingData.percent}%(${existingData.current}/${existingData.total})已经更高`);
}
// 在控制台显示当前保存的所有进度数据(调试用)
console.log('当前所有日期的进度数据:', progressData);
}
function getCurrentDateFromUrl() {
// 尝试从URL中提取日期
const urlParams = new URLSearchParams(window.location.search);
const dateParam = urlParams.get('date');
if (dateParam) {
return dateParam;
} else {
// 如果没有日期参数,使用当前日期
return formatDate(new Date());
}
}
function getProgressForDate(dateStr) {
const progressData = GM_getValue('paperProgress', {});
const dateProgress = progressData[dateStr];
// 如果有进度数据并且是新格式,返回详细信息
if (dateProgress && typeof dateProgress === 'object') {
return dateProgress;
}
// 如果是旧格式(仅百分比)
else if (dateProgress) {
return {
percent: dateProgress,
current: 0,
total: 0,
lastUpdated: null
};
}
// 如果没有进度数据
else {
return {
percent: 0,
current: 0,
total: 0,
lastUpdated: null
};
}
}
function updateCalendarUI() {
// 获取所有日期单元格
const dayCells = document.querySelectorAll('.calendar-day');
dayCells.forEach(cell => {
const dayNumber = cell.querySelector('.day-number');
if (dayNumber) {
const day = parseInt(dayNumber.textContent);
if (!isNaN(day)) {
// 从日历头部获取当前月份和年份
const headerText = document.querySelector('.calendar-header div').textContent;
const monthNames = ["一月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "十一月", "十二月"];
const monthName = headerText.split(' ')[0];
const year = parseInt(headerText.split(' ')[1]);
const month = monthNames.indexOf(monthName);
// 格式化日期
const dateStr = formatDate(new Date(year, month, day));
// 获取这个日期的进度
const progress = getProgressForDate(dateStr);
// 清除现有的进度显示
const oldPercent = cell.querySelector('.progress-percent');
if (oldPercent) {
cell.removeChild(oldPercent);
}
const oldTotal = cell.querySelector('.progress-total');
if (oldTotal) {
cell.removeChild(oldTotal);
}
// 移除任何现有的✓标记
const oldCheckmark = cell.querySelector('div[style*="position: absolute"]');
if (oldCheckmark) {
cell.removeChild(oldCheckmark);
}
// 添加新的进度显示
if (progress.percent > 0) {
// 添加进度百分比
const percentDiv = document.createElement('div');
percentDiv.className = 'progress-percent';
percentDiv.textContent = `${progress.percent}%`;
cell.appendChild(percentDiv);
// 添加总数信息
if (progress.total > 0) {
const totalDiv = document.createElement('div');
totalDiv.className = 'progress-total';
totalDiv.textContent = `${progress.current}/${progress.total}`;
cell.appendChild(totalDiv);
}
// 如果是手动完成的,添加一个✓标记
if (progress.manuallyCompleted) {
const checkmarkDiv = document.createElement('div');
checkmarkDiv.style.position = 'absolute';
checkmarkDiv.style.top = '2px';
checkmarkDiv.style.right = '2px';
checkmarkDiv.style.fontSize = '10px';
checkmarkDiv.style.color = '#fff';
checkmarkDiv.style.fontWeight = 'bold';
checkmarkDiv.textContent = '✓';
cell.appendChild(checkmarkDiv);
}
}
// 移除现有的进度类
cell.classList.remove('no-progress', 'partial-progress', 'complete-progress');
// 根据进度应用适当的类
if (progress.percent === 0) {
cell.classList.add('no-progress');
cell.title = '';
} else if (progress.percent < 100) {
cell.classList.add('partial-progress');
cell.title = `进度: ${progress.percent}%(${progress.current}/${progress.total}篇论文)`;
} else {
cell.classList.add('complete-progress');
cell.title = `已完成: 100%(${progress.total}篇论文)`;
if (progress.manuallyCompleted) {
cell.title += '(手动标记)';
}
}
// 检查是否为当前日期
const currentDate = new Date();
if (day === currentDate.getDate() && month === currentDate.getMonth() && year === currentDate.getFullYear()) {
cell.classList.add('current-day');
cell.title = (cell.title ? cell.title + ' (今天)' : '今天');
}
}
}
});
}
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
})();