您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
增强 Hexo 插件页面,获取并显示 GitHub 仓库的 star 数和最后更新时间,支持排序和缓存
// ==UserScript== // @name Hexo.io 插件页面增强:star数、更新时间 // @name:en Hexo.io plugin page enhancements: star count, update time // @namespace http://tampermonkey.net/ // @version 0.1 // @description 增强 Hexo 插件页面,获取并显示 GitHub 仓库的 star 数和最后更新时间,支持排序和缓存 // @description:en Enhance the Hexo plugin page to obtain and display the number of stars and last update time of the GitHub repository, and support sorting and caching // @author 二次蓝 // @match https://hexo.io/plugins/ // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect api.github.com // ==/UserScript== (function () { 'use strict'; const PLUGIN_SELECTOR = '.plugin'; const GITHUB_API = 'https://api.github.com/repos/'; const plugins = []; const CACHE_KEY = 'hexo_plugin_info_cache'; const TOKEN_KEY = 'github_token'; const CACHE_DURATION = 1000 * 60 * 60 * 24; const THEME_COLOR = '#5AC8FA'; const SVG_ICONS = { settings: `<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12A3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5a3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97c0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1c0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66Z"/></svg>`, close: `<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z"/></svg>`, rocket: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>`, trash: `<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M9 3V4H4V6H5V19C5 20.1 5.9 21 7 21H17C18.1 21 19 20.1 19 19V6H20V4H15V3H9M7 6H17V19H7V6M9 8V17H11V8H9M13 8V17H15V8H13Z"/></svg>`, key: `<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M7 14C5.9 14 5 13.1 5 12S5.9 10 7 10 9 10.9 9 12 8.1 14 7 14M12.65 10C11.83 7.67 9.61 6 7 6C3.69 6 1 8.69 1 12S3.69 18 7 18C9.61 18 11.83 16.33 12.65 14H17V18H21V14H23V10H12.65Z"/></svg>`, sort: `<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M18 21L14 17H17V7H14L18 3L22 7H19V17H22M2 19V17H12V19M2 13V11H9V13M2 7V5H6V7H2Z"/></svg>`, star: `<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M12 17.27L18.18 21L16.54 13.97L22 9.24L14.81 8.62L12 2L9.19 8.62L2 9.24L7.46 13.97L5.82 21L12 17.27Z"/></svg>`, calendar: `<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M19 3H18V1H16V3H8V1H6V3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M19 19H5V8H19V19M7 10H12V15H7"/></svg>`, warning: `<svg width="16" height="16" viewBox="0 0 24 24" fill="#ff6b6b"><path d="M13 14H11V9H13M13 18H11V16H13M1 21H23L12 2L1 21Z"/></svg>` }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const extractRepo = (url) => { const match = url.match(/github\.com\/([\w-]+\/[\w.-]+)/); return match ? match[1] : null; }; function checkCacheExpiry() { try { const raw = GM_getValue(CACHE_KEY, null); if (!raw) return { hasExpired: false, total: 0, expired: 0 }; const parsed = JSON.parse(raw); const now = Date.now(); let total = 0; let expired = 0; Object.entries(parsed).forEach(([, v]) => { total++; if (now - v.timestamp >= CACHE_DURATION) { expired++; } }); return { hasExpired: expired > 0, total, expired }; } catch { return { hasExpired: false, total: 0, expired: 0 }; } } function displayCachedData() { const pluginElements = Array.from(document.querySelectorAll(PLUGIN_SELECTOR)); const cache = loadCache(); let displayed = 0; pluginElements.forEach(el => { const a = el.querySelector('a[href*="github.com"]'); const repo = a ? extractRepo(a.href) : null; if (!repo || !cache[repo]) return; const data = cache[repo].data; const isExpired = Date.now() - cache[repo].timestamp >= CACHE_DURATION; let extra = el.querySelector('.github-info'); if (!extra) { extra = document.createElement('div'); extra.className = 'github-info'; extra.style.cssText = ` font-size: 12px; color: #666; margin-top: 5px; padding: 4px 8px; background: ${isExpired ? '#fff3cd' : '#f8f9fa'}; border-radius: 4px; border-left: 3px solid ${isExpired ? '#ffc107' : THEME_COLOR}; `; el.appendChild(extra); } const updatedDate = data.updated ? new Date(data.updated).toLocaleDateString('zh-CN') : '未知'; extra.innerHTML = ` <span style="color: #ffc107; vertical-align: middle;">${SVG_ICONS.star}</span> ${data.stars} | <span style="color: #28a745; vertical-align: middle;">${SVG_ICONS.calendar}</span> ${updatedDate} ${isExpired ? `<span style="color: #ff6b6b; margin-left: 8px; vertical-align: middle;">${SVG_ICONS.warning} 数据已过期</span>` : ''} `; plugins.push({ element: el, repo: repo, stars: data.stars, updated: data.updated }); displayed++; }); return displayed; } function loadCache() { try { const raw = GM_getValue(CACHE_KEY, null); if (!raw) return {}; const parsed = JSON.parse(raw); const now = Date.now(); return Object.fromEntries( Object.entries(parsed).filter(([, v]) => now - v.timestamp < CACHE_DURATION) ); } catch (e) { console.warn('缓存加载失败:', e); return {}; } } function saveCache(cache) { try { GM_setValue(CACHE_KEY, JSON.stringify(cache)); } catch (e) { console.warn('缓存保存失败:', e); } } const fetchGitHubInfo = (repo, cache) => { return new Promise((resolve) => { if (cache[repo]) { return resolve(cache[repo].data); } const token = GM_getValue(TOKEN_KEY, ''); const headers = { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'Hexo-Plugin-Enhancer/0.1' }; if (token) { headers['Authorization'] = `token ${token}`; } GM_xmlhttpRequest({ method: 'GET', url: GITHUB_API + repo, headers: headers, timeout: 15000, onload: function (response) { if (response.status === 200) { try { const data = JSON.parse(response.responseText); const result = { stars: data.stargazers_count || 0, updated: data.pushed_at || data.updated_at || '' }; cache[repo] = { timestamp: Date.now(), data: result }; resolve(result); } catch (e) { console.warn(`解析 ${repo} 数据失败:`, e); resolve(null); } } else if (response.status === 403) { console.warn(`API限制达到上限 ${repo}: ${response.status}`); resolve(null); } else { console.warn(`获取 ${repo} 信息失败: ${response.status}`); resolve(null); } }, onerror: (error) => { console.warn(`请求 ${repo} 失败:`, error); resolve(null); }, ontimeout: () => { console.warn(`请求 ${repo} 超时`); resolve(null); } }); }); }; let loadingBox = null; function showLoadingMessage(total = 0, current = 0) { if (!loadingBox) { loadingBox = document.createElement('div'); loadingBox.id = 'hexo-plugin-loading-info'; loadingBox.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; background: linear-gradient(90deg, #667eea, #764ba2); color: #fff; padding: 12px; font-size: 14px; z-index: 9999; text-align: center; box-shadow: 0 2px 10px rgba(0,0,0,0.3); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; document.body.appendChild(loadingBox); } const percentage = total > 0 ? Math.round((current / total) * 100) : 0; const token = GM_getValue(TOKEN_KEY, ''); const mode = token ? '高速模式(并发50个)' : '标准模式(并发20个)'; loadingBox.innerHTML = ` <div>${SVG_ICONS.rocket} 正在抓取 GitHub 信息...(${current} / ${total})- ${percentage}% | ${mode}</div> <div style="background: rgba(255,255,255,0.3); height: 4px; margin-top: 8px; border-radius: 2px;"> <div style="background: #fff; height: 100%; width: ${percentage}%; border-radius: 2px; transition: width 0.3s ease;"></div> </div> `; } function showFinishedMessage(total, cached = 0) { if (loadingBox) { loadingBox.style.background = 'linear-gradient(90deg, #56ab2f, #a8e6cf)'; loadingBox.innerHTML = `${SVG_ICONS.rocket} 抓取完成(共 ${total} 项,缓存命中 ${cached} 项)`; setTimeout(() => { if (loadingBox && loadingBox.parentNode) { loadingBox.remove(); loadingBox = null; } }, 3000); } } async function enhance() { const pluginElements = Array.from(document.querySelectorAll(PLUGIN_SELECTOR)); if (!pluginElements.length) { console.warn('未找到插件元素'); return; } const total = pluginElements.length; let current = 0; let cached = 0; const cache = loadCache(); showLoadingMessage(total, current); const token = GM_getValue(TOKEN_KEY, ''); const concurrency = token ? 50 : 20; const pluginInfos = []; for (const el of pluginElements) { const a = el.querySelector('a[href*="github.com"]'); const repo = a ? extractRepo(a.href) : null; if (repo) { pluginInfos.push({ element: el, repo: repo, stars: 0, updated: '' }); } } const processBatch = async (batch) => { const promises = batch.map(async (info) => { const wasCached = !!cache[info.repo]; const data = await fetchGitHubInfo(info.repo, cache); if (wasCached) cached++; if (data) { info.stars = data.stars; info.updated = data.updated; let extra = info.element.querySelector('.github-info'); if (!extra) { extra = document.createElement('div'); extra.className = 'github-info'; extra.style.cssText = ` font-size: 12px; color: #666; margin-top: 5px; padding: 4px 8px; background: #f8f9fa; border-radius: 4px; border-left: 3px solid #007bff; `; info.element.appendChild(extra); } const updatedDate = data.updated ? new Date(data.updated).toLocaleDateString('zh-CN') : '未知'; extra.innerHTML = ` <span style="color: #ffc107; vertical-align: middle;">${SVG_ICONS.star}</span> ${data.stars} | <span style="color: #28a745; vertical-align: middle;">${SVG_ICONS.calendar}</span> ${updatedDate} `; } plugins.push(info); current++; showLoadingMessage(total, current); return info; }); return Promise.all(promises); }; for (let i = 0; i < pluginInfos.length; i += concurrency) { const batch = pluginInfos.slice(i, i + concurrency); await processBatch(batch); // 每批之间稍微延迟,避免请求过于密集 if (i + concurrency < pluginInfos.length) { await sleep(token ? 100 : 500); } saveCache(cache); } showFinishedMessage(total, cached); injectSortControls(); } function injectControls() { const cacheStatus = checkCacheExpiry(); const floatingBall = document.createElement('div'); floatingBall.id = 'hexo-plugin-floating-ball'; const ballContent = document.createElement('div'); ballContent.style.cssText = ` display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; position: relative; `; const mainIcon = document.createElement('div'); mainIcon.innerHTML = SVG_ICONS.settings; mainIcon.style.cssText = ` color: white; display: flex; align-items: center; justify-content: center; `; ballContent.appendChild(mainIcon); if (cacheStatus.hasExpired) { const badge = document.createElement('div'); badge.innerHTML = cacheStatus.expired; badge.style.cssText = ` position: absolute; top: -5px; right: -5px; background: #ff6b6b; color: white; border-radius: 50%; width: 18px; height: 18px; font-size: 10px; font-weight: bold; display: flex; align-items: center; justify-content: center; border: 2px solid white; `; ballContent.appendChild(badge); } floatingBall.appendChild(ballContent); floatingBall.title = cacheStatus.hasExpired ? `Hexo 插件增强设置 (${cacheStatus.expired}项缓存已过期)` : 'Hexo 插件增强设置'; floatingBall.style.cssText = ` position: fixed; top: 50%; right: 20px; width: 50px; height: 50px; background: linear-gradient(135deg, ${THEME_COLOR}, #4A9EFF); border-radius: 50%; cursor: pointer; z-index: 10000; box-shadow: 0 4px 12px rgba(90, 200, 250, 0.3); transition: all 0.3s ease; user-select: none; `; // 拖拽功能 let isDragging = false; let hasDragged = false; let startX, startY, startLeft, startTop; let mouseDownTime = 0; const DRAG_THRESHOLD = 5; const CLICK_TIME_THRESHOLD = 200; floatingBall.addEventListener('mousedown', (e) => { mouseDownTime = Date.now(); isDragging = false; hasDragged = false; startX = e.clientX; startY = e.clientY; const rect = floatingBall.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (mouseDownTime === 0) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // 只有移动距离超过阈值才开始拖拽 if (distance > DRAG_THRESHOLD && !isDragging) { isDragging = true; hasDragged = true; floatingBall.style.cursor = 'grabbing'; } if (isDragging) { let newLeft = startLeft + deltaX; let newTop = startTop + deltaY; // 边界限制 const maxLeft = window.innerWidth - 50; const maxTop = window.innerHeight - 50; newLeft = Math.max(0, Math.min(newLeft, maxLeft)); newTop = Math.max(0, Math.min(newTop, maxTop)); floatingBall.style.left = newLeft + 'px'; floatingBall.style.top = newTop + 'px'; floatingBall.style.right = 'auto'; floatingBall.style.transform = 'none'; } }); document.addEventListener('mouseup', () => { if (mouseDownTime > 0) { const clickDuration = Date.now() - mouseDownTime; // 重置状态 mouseDownTime = 0; if (isDragging) { isDragging = false; floatingBall.style.cursor = 'pointer'; } if (!hasDragged && clickDuration < CLICK_TIME_THRESHOLD) { // 延迟触发点击,确保拖拽状态已重置 setTimeout(() => { floatingBall.dispatchEvent(new Event('ballclick')); }, 10); } } }); floatingBall.addEventListener('mouseenter', () => { if (!isDragging) { const currentTransform = floatingBall.style.transform; if (currentTransform.includes('translateY(-50%)')) { floatingBall.style.transform = 'translateY(-50%) scale(1.1)'; } else { floatingBall.style.transform = 'scale(1.1)'; } floatingBall.style.boxShadow = '0 6px 20px rgba(90, 200, 250, 0.5)'; } }); floatingBall.addEventListener('mouseleave', () => { if (!isDragging) { const currentTransform = floatingBall.style.transform; if (currentTransform.includes('translateY(-50%)')) { floatingBall.style.transform = 'translateY(-50%)'; } else { floatingBall.style.transform = 'none'; } floatingBall.style.boxShadow = '0 4px 12px rgba(90, 200, 250, 0.3)'; } }); document.body.appendChild(floatingBall); // 立即显示缓存数据 const displayedCount = displayCachedData(); if (displayedCount > 0) { console.log(`已显示 ${displayedCount} 项缓存数据`); // 延迟启用排序控制,确保DOM已完全渲染 setTimeout(() => { injectSortControls(); }, 100); } const container = document.createElement('div'); container.id = 'hexo-plugin-controls'; container.style.cssText = ` position: fixed; top: 50%; right: 80px; width: 400px; max-height: 80vh; overflow-y: auto; padding: 20px; background: white; border-radius: 12px; border: 1px solid #dee2e6; box-shadow: 0 8px 32px rgba(90, 200, 250, 0.15); z-index: 9999; transform: translateY(-50%) scale(0); opacity: 0; transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); transform-origin: right center; `; const title = document.createElement('div'); title.innerHTML = 'Hexo 插件增强设置'; title.style.cssText = ` font-size: 18px; font-weight: 600; color: #333; margin-bottom: 20px; text-align: center; border-bottom: 2px solid ${THEME_COLOR}; padding-bottom: 10px; `; container.appendChild(title); document.body.appendChild(container); let isOpen = false; floatingBall.addEventListener('ballclick', () => { isOpen = !isOpen; if (isOpen) { container.style.transform = 'translateY(-50%) scale(1)'; container.style.opacity = '1'; mainIcon.innerHTML = SVG_ICONS.close; floatingBall.title = '点击关闭设置面板'; } else { container.style.transform = 'translateY(-50%) scale(0)'; container.style.opacity = '0'; mainIcon.innerHTML = SVG_ICONS.settings; floatingBall.title = cacheStatus.hasExpired ? `Hexo 插件增强设置 (${cacheStatus.expired}项缓存已过期)` : 'Hexo 插件增强设置'; } }); document.addEventListener('click', (e) => { if (isOpen && !container.contains(e.target) && !floatingBall.contains(e.target)) { isOpen = false; container.style.transform = 'translateY(-50%) scale(0)'; container.style.opacity = '0'; mainIcon.innerHTML = SVG_ICONS.settings; floatingBall.title = cacheStatus.hasExpired ? `Hexo 插件增强设置 (${cacheStatus.expired}项缓存已过期)` : 'Hexo 插件增强设置'; } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isOpen) { isOpen = false; container.style.transform = 'translateY(-50%) scale(0)'; container.style.opacity = '0'; mainIcon.innerHTML = SVG_ICONS.settings; floatingBall.title = cacheStatus.hasExpired ? `Hexo 插件增强设置 (${cacheStatus.expired}项缓存已过期)` : 'Hexo 插件增强设置'; } }); const tokenSection = document.createElement('div'); tokenSection.style.cssText = ` margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid ${THEME_COLOR}; `; const tokenLabel = document.createElement('label'); tokenLabel.innerHTML = `${SVG_ICONS.key} GitHub Token`; tokenLabel.style.cssText = ` display: flex; align-items: center; gap: 8px; font-weight: 600; margin-bottom: 8px; color: #495057; font-size: 14px; `; const tokenDesc = document.createElement('div'); tokenDesc.textContent = '不设置每小时每IP只能请求60次api,设置后可以提高到5,000次'; tokenDesc.style.cssText = ` font-size: 12px; color: #6c757d; margin-bottom: 10px; `; tokenSection.appendChild(tokenLabel); tokenSection.appendChild(tokenDesc); const tokenInput = document.createElement('input'); tokenInput.type = 'password'; tokenInput.placeholder = '输入GitHub Personal Access Token'; tokenInput.value = GM_getValue(TOKEN_KEY, ''); tokenInput.style.cssText = ` width: 100%; padding: 8px 12px; border: 1px solid #ced4da; border-radius: 6px; margin-bottom: 10px; font-size: 14px; box-sizing: border-box; transition: border-color 0.3s; `; tokenInput.addEventListener('focus', () => { tokenInput.style.borderColor = THEME_COLOR; }); tokenInput.addEventListener('blur', () => { tokenInput.style.borderColor = '#ced4da'; }); tokenSection.appendChild(tokenInput); const saveTokenBtn = document.createElement('button'); saveTokenBtn.textContent = '保存Token'; saveTokenBtn.style.cssText = ` padding: 8px 16px; background: linear-gradient(135deg, ${THEME_COLOR}, #4A9FE7); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; margin-right: 10px; transition: all 0.3s; `; saveTokenBtn.addEventListener('mouseenter', () => { saveTokenBtn.style.transform = 'translateY(-1px)'; saveTokenBtn.style.boxShadow = `0 4px 12px rgba(90, 200, 250, 0.3)`; }); saveTokenBtn.addEventListener('mouseleave', () => { saveTokenBtn.style.transform = 'none'; saveTokenBtn.style.boxShadow = 'none'; }); saveTokenBtn.addEventListener('click', () => { GM_setValue(TOKEN_KEY, tokenInput.value.trim()); alert('Token已保存!'); }); tokenSection.appendChild(saveTokenBtn); const tokenHelp = document.createElement('div'); tokenHelp.innerHTML = ` <small style="color: #6c757d; line-height: 1.4;"> 💡 在 <a href="https://github.com/settings/tokens" target="_blank" style="color: #007bff;">GitHub Settings</a> 创建Token,无需任何权限,填写好名称描述,即可提高API限制 </small> `; tokenSection.appendChild(tokenHelp); container.appendChild(tokenSection); const controlSection = document.createElement('div'); controlSection.style.cssText = ` margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid ${THEME_COLOR}; `; const startBtn = document.createElement('button'); startBtn.innerHTML = `${SVG_ICONS.rocket} 开始获取插件信息`; startBtn.style.cssText = ` width: 100%; padding: 12px 16px; background: linear-gradient(135deg, ${THEME_COLOR}, #4A9FE7); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; margin-bottom: 10px; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px; `; startBtn.addEventListener('mouseenter', () => { startBtn.style.transform = 'translateY(-2px)'; startBtn.style.boxShadow = '0 4px 16px rgba(90, 200, 250, 0.4)'; }); startBtn.addEventListener('mouseleave', () => { startBtn.style.transform = 'none'; startBtn.style.boxShadow = 'none'; }); startBtn.addEventListener('click', enhance); controlSection.appendChild(startBtn); const clearCacheBtn = document.createElement('button'); clearCacheBtn.innerHTML = `${SVG_ICONS.trash} 清除缓存`; clearCacheBtn.style.cssText = ` width: 100%; padding: 12px 16px; background: linear-gradient(135deg, #FF6B6B, #FF5252); color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px; `; clearCacheBtn.addEventListener('mouseenter', () => { clearCacheBtn.style.transform = 'translateY(-2px)'; clearCacheBtn.style.boxShadow = '0 4px 16px rgba(255, 107, 107, 0.4)'; }); clearCacheBtn.addEventListener('mouseleave', () => { clearCacheBtn.style.transform = 'none'; clearCacheBtn.style.boxShadow = 'none'; }); clearCacheBtn.addEventListener('click', () => { GM_setValue(CACHE_KEY, '{}'); alert('缓存已清除!'); }); controlSection.appendChild(clearCacheBtn); container.appendChild(controlSection); const sortSection = document.createElement('div'); sortSection.className = 'sort-section'; sortSection.style.cssText = ` padding: 15px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid ${THEME_COLOR}; `; const label = document.createElement('label'); label.innerHTML = `${SVG_ICONS.sort} 排序方式`; label.style.cssText = ` display: flex; align-items: center; gap: 8px; font-weight: 600; margin-bottom: 10px; color: #495057; font-size: 14px; `; sortSection.appendChild(label); const select = document.createElement('select'); select.style.cssText = ` width: 100%; padding: 8px 12px; border: 1px solid #ced4da; border-radius: 6px; background: white; font-size: 14px; cursor: pointer; box-sizing: border-box; transition: border-color 0.3s; `; select.addEventListener('focus', () => { select.style.borderColor = THEME_COLOR; }); select.addEventListener('blur', () => { select.style.borderColor = '#ced4da'; }); select.innerHTML = ` <option value="default">默认顺序</option> <option value="stars">⭐ Star 数(高到低)</option> <option value="updated">📅 更新时间(新到旧)</option> `; select.disabled = true; select.style.opacity = '0.5'; select.addEventListener('change', () => { const val = select.value; const pluginElements = Array.from(document.querySelectorAll(PLUGIN_SELECTOR)); if (pluginElements.length === 0) { console.warn('未找到插件元素,无法排序'); return; } const pluginsWithData = pluginElements.map(el => { const a = el.querySelector('a[href*="github.com"]'); const repo = a ? extractRepo(a.href) : null; const githubInfo = el.querySelector('.github-info'); let stars = 0; let updated = ''; if (githubInfo && repo) { // 从缓存或全局plugins数组中获取数据 const cachedPlugin = plugins.find(p => p.repo === repo); if (cachedPlugin) { stars = cachedPlugin.stars || 0; updated = cachedPlugin.updated || ''; } else { // 从缓存中获取数据 const cache = loadCache(); if (cache[repo]) { stars = cache[repo].data.stars || 0; updated = cache[repo].data.updated || ''; } } } return { element: el, repo: repo, stars: stars, updated: updated }; }); // 根据选择的排序方式进行排序 let sorted = [...pluginsWithData]; if (val === 'stars') { sorted.sort((a, b) => b.stars - a.stars); } else if (val === 'updated') { sorted.sort((a, b) => { const dateA = new Date(a.updated || 0); const dateB = new Date(b.updated || 0); return dateB - dateA; }); } const firstPlugin = pluginElements[0]; const parent = firstPlugin.parentNode; if (parent) { sorted.forEach(info => { parent.appendChild(info.element); }); console.log(`已按${val === 'stars' ? 'Star数' : val === 'updated' ? '更新时间' : '默认顺序'}排序`); } else { console.warn('未找到插件父容器,无法排序'); } }); sortSection.appendChild(select); container.appendChild(sortSection); const authorSection = document.createElement('div'); authorSection.style.cssText = ` padding: 12px 15px 0; text-align: center; margin-top: 10px; `; const authorInfo = document.createElement('div'); authorInfo.innerHTML = 'Made with ❤️ by 二次蓝 <a href="https://blog.ercilan.cn" target="_blank" style="color: #007bff; text-decoration: none; font-weight: 500;">https://blog.ercilan.cn</a>'; authorInfo.style.cssText = ` font-size: 12px; color: #6c757d; line-height: 1.4; `; authorSection.appendChild(authorInfo); container.appendChild(authorSection); } function injectSortControls() { const container = document.querySelector('#hexo-plugin-controls'); if (!container) return; const sortSection = container.querySelector('.sort-section'); if (!sortSection) return; const select = sortSection.querySelector('select'); if (!select) return; if (plugins.length > 0) { select.disabled = false; select.style.opacity = '1'; console.log('排序控制已启用,可排序插件数量:', plugins.length); } else { const cache = loadCache(); const cacheCount = Object.keys(cache).length; if (cacheCount > 0) { select.disabled = false; select.style.opacity = '1'; console.log('基于缓存数据启用排序控制,缓存项数量:', cacheCount); } } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(injectControls, 500); }); } else { setTimeout(injectControls, 500); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址