feat: 添加文档阅读进度追踪功能
- 新增阅读进度条,显示页面滚动进度 - 实现侧边栏绿色小勾标记,标识已阅读文档 - 阅读记录保存在浏览器本地存储 - 支持中文路径的 URL 编码/解码 - 添加调试功能方便问题排查 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d03d01d20f
commit
6a196f1925
|
|
@ -21,6 +21,8 @@ GitLink(确实开源)是CCF官方指定的开源创新服务平台,旨在
|
|||
# 帮助文档
|
||||
帮助文档有助于您全面了解GitLink平台,让我们一起为开源创新贡献力量!
|
||||
|
||||
<ProgressPanel />
|
||||
|
||||
<div class="row">
|
||||
<div class="col col--12">
|
||||
<section class="row list">
|
||||
|
|
@ -32,7 +34,7 @@ GitLink(确实开源)是CCF官方指定的开源创新服务平台,旨在
|
|||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/代码库管理/仓库创建">
|
||||
<h2 class="text--truncate cardTitle" title="代码库管理">代码库管理</h2>
|
||||
<p>代码库使用及设置[9个文档]</p>
|
||||
<p>代码库使用及设置[8个文档]</p>
|
||||
</a></article>
|
||||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/组织管理/组织简介">
|
||||
|
|
@ -60,9 +62,9 @@ GitLink(确实开源)是CCF官方指定的开源创新服务平台,旨在
|
|||
<p>维基(Wiki)使用及设置[2个文档]</p>
|
||||
</a></article>
|
||||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/bot市场/bot安装">
|
||||
<h2 class="text--truncate cardTitle" title="bot市场">bot市场</h2>
|
||||
<p>bot市场使用及设置[3个文档]</p>
|
||||
<a class="card padding--lg cardContainer" href="/Bot市场/bot安装">
|
||||
<h2 class="text--truncate cardTitle" title="Bot市场">Bot市场</h2>
|
||||
<p>Bot市场使用及设置[4个文档]</p>
|
||||
</a></article>
|
||||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/第三方服务/跨平台代码同步">
|
||||
|
|
|
|||
|
|
@ -16,7 +16,13 @@ module.exports = {
|
|||
favicon: 'img/icon.ico',
|
||||
organizationName: 'luffyZh', // Usually your GitHub org/user name.
|
||||
projectName: 'docusaurus-luffyzh-website', // Usually your repo name.
|
||||
scripts: [],
|
||||
scripts: [
|
||||
{
|
||||
src: '/js/reading-progress.js',
|
||||
async: true,
|
||||
defer: true,
|
||||
},
|
||||
],
|
||||
// stylesheets: ['styles/dark-mode.css'],
|
||||
themeConfig: {
|
||||
docs:{
|
||||
|
|
@ -176,6 +182,18 @@ module.exports = {
|
|||
// sidebarPath: require.resolve('./sidebars.js'),
|
||||
editUrl:'https://www.gitlink.org.cn/Gitlink/gitlink_help_center/tree/master/',
|
||||
routeBasePath: "/",
|
||||
remarkPlugins: [
|
||||
async () => {
|
||||
const { visit } = await import('unist-util-visit');
|
||||
return (tree) => {
|
||||
visit(tree, 'mdxJsxFlowElement', (node) => {
|
||||
if (node.name === 'ProgressPanel') {
|
||||
node.attributes = node.attributes || [];
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
],
|
||||
},
|
||||
theme: {
|
||||
customCss: require.resolve('./src/css/custom.css'),
|
||||
|
|
|
|||
|
|
@ -160,7 +160,72 @@ html[data-theme='dark'] .docusaurus-highlight-code-line {
|
|||
background-color: #cce0ff !important;
|
||||
box-shadow: 0 4px 12px rgba(70, 106, 255, 0.15);
|
||||
}
|
||||
/* 阅读进度条 */
|
||||
.reading-progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: rgba(70, 106, 255, 0.1);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.reading-progress__bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #466aff, #6b8cff);
|
||||
transition: width 0.15s ease-out;
|
||||
}
|
||||
|
||||
/* 侧边栏完成标记 */
|
||||
.sidebar-completed-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: #22c55e;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 进度面板 */
|
||||
.progress-panel {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.progress-panel__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-panel__bar {
|
||||
height: 8px;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-panel__fill {
|
||||
height: 100%;
|
||||
background-color: #22c55e;
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-panel__text {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
/* ===== 文档投票组件样式 ===== */
|
||||
.vote-container {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,271 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
const STORAGE_KEY = 'gitlink_reading_progress';
|
||||
const BADGE_CLASS = 'reading-progress-badge';
|
||||
|
||||
function getReadDocs() {
|
||||
try {
|
||||
const data = localStorage.getItem(STORAGE_KEY);
|
||||
return data ? JSON.parse(data) : [];
|
||||
} catch (e) {
|
||||
console.error('读取进度失败:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function saveReadDocs(docs) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(docs));
|
||||
} catch (e) {
|
||||
console.error('保存进度失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePath(path) {
|
||||
if (!path) return '/';
|
||||
|
||||
// URL 解码(处理中文路径)
|
||||
try {
|
||||
path = decodeURIComponent(path);
|
||||
} catch (e) {
|
||||
// 如果解码失败,保持原样
|
||||
}
|
||||
|
||||
path = path.replace(/^\/+/, '').replace(/\/+$/, '');
|
||||
return '/' + path;
|
||||
}
|
||||
|
||||
function markAsRead() {
|
||||
const currentPath = normalizePath(window.location.pathname);
|
||||
const readDocs = getReadDocs();
|
||||
|
||||
if (!readDocs.includes(currentPath)) {
|
||||
readDocs.push(currentPath);
|
||||
saveReadDocs(readDocs);
|
||||
console.log('✅ 已标记为已阅读:', currentPath);
|
||||
updateBadges();
|
||||
} else {
|
||||
console.log('ℹ️ 已经标记过了:', currentPath);
|
||||
}
|
||||
}
|
||||
|
||||
function isDocRead(path) {
|
||||
const readDocs = getReadDocs();
|
||||
const normalizedPath = normalizePath(path);
|
||||
return readDocs.some(docPath => normalizePath(docPath) === normalizedPath);
|
||||
}
|
||||
|
||||
function createBadge() {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = BADGE_CLASS;
|
||||
badge.textContent = '✓';
|
||||
badge.style.cssText = `
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
background-color: #22c55e !important;
|
||||
color: white !important;
|
||||
border-radius: 50% !important;
|
||||
font-size: 11px !important;
|
||||
font-weight: bold !important;
|
||||
flex-shrink: 0 !important;
|
||||
margin-left: 8px !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
position: relative !important;
|
||||
`;
|
||||
return badge;
|
||||
}
|
||||
|
||||
function updateBadges() {
|
||||
const readDocs = getReadDocs();
|
||||
console.log('📚 已阅读的文档数:', readDocs.length, readDocs);
|
||||
|
||||
if (readDocs.length === 0) {
|
||||
console.log('⚠️ 还没有已阅读的文档');
|
||||
return;
|
||||
}
|
||||
|
||||
// 清除所有旧标记
|
||||
document.querySelectorAll('.' + BADGE_CLASS).forEach(badge => badge.remove());
|
||||
|
||||
// 获取所有可能的链接元素
|
||||
const selectors = [
|
||||
'a.menu__link',
|
||||
'a.menu-link',
|
||||
'.menu a[href^="/"]',
|
||||
'.sidebar a[href^="/"]',
|
||||
'nav a[href^="/"]',
|
||||
'[class*="sidebar"] a[href^="/"]',
|
||||
'[class*="menu"] a[href^="/"]'
|
||||
];
|
||||
|
||||
let totalLinks = 0;
|
||||
let matchedLinks = 0;
|
||||
|
||||
selectors.forEach(selector => {
|
||||
const links = document.querySelectorAll(selector);
|
||||
links.forEach(link => {
|
||||
totalLinks++;
|
||||
const href = link.getAttribute('href');
|
||||
|
||||
if (href && !href.startsWith('http') && !href.startsWith('#') && href !== '/') {
|
||||
const linkPath = normalizePath(href);
|
||||
|
||||
if (isDocRead(linkPath)) {
|
||||
matchedLinks++;
|
||||
if (!link.querySelector('.' + BADGE_CLASS)) {
|
||||
const badge = createBadge();
|
||||
link.appendChild(badge);
|
||||
console.log('✓ 添加标记:', linkPath, '->', link.textContent.trim().substring(0, 30));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`🔍 检查了 ${totalLinks} 个链接,匹配了 ${matchedLinks} 个已阅读文档`);
|
||||
}
|
||||
|
||||
function initProgressBar() {
|
||||
if (document.querySelector('[data-reading-progress-bar]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const progressBar = document.createElement('div');
|
||||
progressBar.setAttribute('data-reading-progress-bar', 'true');
|
||||
progressBar.style.cssText = `
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 4px !important;
|
||||
background-color: rgba(70, 106, 255, 0.1) !important;
|
||||
z-index: 9999 !important;
|
||||
pointer-events: none !important;
|
||||
`;
|
||||
|
||||
const bar = document.createElement('div');
|
||||
bar.style.cssText = `
|
||||
height: 100% !important;
|
||||
background: linear-gradient(90deg, #466aff, #6b8cff) !important;
|
||||
width: 0% !important;
|
||||
transition: width 0.15s ease-out !important;
|
||||
`;
|
||||
|
||||
progressBar.appendChild(bar);
|
||||
document.body.appendChild(progressBar);
|
||||
|
||||
let ticking = false;
|
||||
window.addEventListener('scroll', () => {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(() => {
|
||||
const windowHeight = window.innerHeight;
|
||||
const documentHeight = document.documentElement.scrollHeight - windowHeight;
|
||||
const scrolled = window.scrollY;
|
||||
const progress = (scrolled / documentHeight) * 100;
|
||||
bar.style.width = Math.min(progress, 100) + '%';
|
||||
ticking = false;
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initScrollDetection() {
|
||||
let hasMarked = false;
|
||||
|
||||
function checkScroll() {
|
||||
const scrollPosition = window.innerHeight + window.scrollY;
|
||||
const pageHeight = document.documentElement.scrollHeight;
|
||||
const threshold = 200;
|
||||
const progress = Math.min((scrollPosition / pageHeight) * 100, 100);
|
||||
|
||||
if (!hasMarked && (progress >= 90 || (pageHeight - scrollPosition < threshold))) {
|
||||
hasMarked = true;
|
||||
console.log('📖 滚动进度: ' + Math.round(progress) + '%,标记为已阅读');
|
||||
markAsRead();
|
||||
setTimeout(() => { hasMarked = false; }, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', () => checkScroll());
|
||||
setTimeout(checkScroll, 1000);
|
||||
}
|
||||
|
||||
function init() {
|
||||
console.log('🚀 初始化阅读进度追踪...');
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => setTimeout(initAll, 500));
|
||||
} else {
|
||||
setTimeout(initAll, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function initAll() {
|
||||
console.log('⚙️ 启动功能...');
|
||||
initProgressBar();
|
||||
updateBadges();
|
||||
initScrollDetection();
|
||||
|
||||
// 定期更新
|
||||
setInterval(updateBadges, 3000);
|
||||
}
|
||||
|
||||
// 启动
|
||||
init();
|
||||
|
||||
// 监听路由变化
|
||||
let lastUrl = location.href;
|
||||
new MutationObserver(() => {
|
||||
const url = location.href;
|
||||
if (url !== lastUrl) {
|
||||
lastUrl = url;
|
||||
console.log('🔄 页面变化,更新标记');
|
||||
setTimeout(updateBadges, 500);
|
||||
}
|
||||
}).observe(document, { subtree: true, childList: true });
|
||||
|
||||
// 暴露调试接口
|
||||
window.readingProgressDebug = {
|
||||
getReadDocs,
|
||||
markAsRead,
|
||||
updateBadges,
|
||||
clearAll: () => {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
document.querySelectorAll('.' + BADGE_CLASS).forEach(b => b.remove());
|
||||
console.log('🗑️ 已清除所有阅读记录');
|
||||
updateBadges();
|
||||
},
|
||||
test: () => {
|
||||
console.log('🧪 开始测试...');
|
||||
const currentPath = normalizePath(window.location.pathname);
|
||||
console.log('当前路径:', currentPath);
|
||||
console.log('已阅读文档:', getReadDocs());
|
||||
|
||||
const links = document.querySelectorAll('a[href^="/"]');
|
||||
console.log('找到链接数:', links.length);
|
||||
|
||||
links.forEach(link => {
|
||||
const href = link.getAttribute('href');
|
||||
if (href && !href.startsWith('http') && href !== '/') {
|
||||
console.log('链接:', normalizePath(href), '文本:', link.textContent.trim());
|
||||
}
|
||||
});
|
||||
|
||||
updateBadges();
|
||||
}
|
||||
};
|
||||
|
||||
console.log('✅ 阅读进度追踪已加载');
|
||||
console.log('🔧 调试命令:');
|
||||
console.log(' readingProgressDebug.markAsRead() - 手动标记当前页面');
|
||||
console.log(' readingProgressDebug.updateBadges() - 更新侧边栏标记');
|
||||
console.log(' readingProgressDebug.test() - 运行测试');
|
||||
console.log(' readingProgressDebug.clearAll() - 清除所有记录');
|
||||
|
||||
})();
|
||||
Loading…
Reference in New Issue