From 6a196f192519a0c8b6c4aead0e20a2139f91b024 Mon Sep 17 00:00:00 2001 From: Donkey_kevin <2930705585@qq.com> Date: Wed, 13 May 2026 14:54:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E9=98=85=E8=AF=BB=E8=BF=9B=E5=BA=A6=E8=BF=BD=E8=B8=AA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增阅读进度条,显示页面滚动进度 - 实现侧边栏绿色小勾标记,标识已阅读文档 - 阅读记录保存在浏览器本地存储 - 支持中文路径的 URL 编码/解码 - 添加调试功能方便问题排查 Co-Authored-By: Claude Sonnet 4.6 --- docs/intro.md | 10 +- docusaurus.config.js | 20 ++- src/css/custom.css | 65 ++++++++ static/js/reading-progress.js | 271 ++++++++++++++++++++++++++++++++++ 4 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 static/js/reading-progress.js diff --git a/docs/intro.md b/docs/intro.md index 73610ec..204d0e2 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -21,6 +21,8 @@ GitLink(确实开源)是CCF官方指定的开源创新服务平台,旨在 # 帮助文档 帮助文档有助于您全面了解GitLink平台,让我们一起为开源创新贡献力量! + +
@@ -32,7 +34,7 @@ GitLink(确实开源)是CCF官方指定的开源创新服务平台,旨在
diff --git a/docusaurus.config.js b/docusaurus.config.js index 424ef17..3ec14c5 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -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'), diff --git a/src/css/custom.css b/src/css/custom.css index 8f02718..63fc4c4 100644 --- a/src/css/custom.css +++ b/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; diff --git a/static/js/reading-progress.js b/static/js/reading-progress.js new file mode 100644 index 0000000..8b3e62c --- /dev/null +++ b/static/js/reading-progress.js @@ -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() - 清除所有记录'); + +})();