// ==UserScript== // @name bgm相关回复跳转 // @version 0.5 // @description 在web自动识别贴贴过的帖子,提供跳转。 // @match https://bgm.tv/ep/* // @match https://bangumi.tv/ep/* // @match https://chii.in/ep/* // @grant none // @license MIT // @namespace bgm_jump_related_post // @require https://cdn.tailwindcss.com // @downloadURL none // ==/UserScript== // 几个todo,目前自己没这个需求,暂时简化。如果有人需要可以说一声: // - [ ] 除了贴贴也支持回复过的跳转。 // 前期主要支持贴贴跳转 // 后续会增加回复跳转的,因为其实可以搜索,相对没那么着急 // - [ ] 暂时只支持番剧吐槽页面,主要是觉得别的页面也没必要。 // - [x] 暂时只处理每层回复的一楼,这个主要是暂时懒得做先简化一下。也就是不支持比如说第二楼的所有回复贴贴。 // - [ ] 看要不要支持这个框可拖动,要不就是现在这个折叠之后的样式有点小奇怪,就是不应该留东西太外面,这个动画有点太花哨了。整体其实可以更简单一点,写到后面有点后悔了,引入样式库其实已经是极限了,后续应该更精简一点 (function () { 'use strict'; // Load Tailwind CSS const tailwindScript = document.createElement('script'); tailwindScript.src = 'https://cdn.tailwindcss.com'; document.head.appendChild(tailwindScript); const selfUserId = $('.avatar').attr('href').split('/').pop(); // 先完成遍历处理第一层的逻辑 const replyList = $('#comment_list>div'); // 得到所有首层回复 let relatedPostIds = []; // 筛选最终跟当前用户相关的id // replyList.slice(0, 2).each(function () { // 调试场景只看前三个帖子 replyList.each(function () { // 逐个过滤 const firstLevelId = $(this).attr('id'); // 检查一楼 if (checkReplyElementRelated(selfUserId, $(this))) { relatedPostIds.push(firstLevelId); } // 检查楼中楼(二级回复) const subReplies = $(this).find('.topic_sub_reply .sub_reply_bg'); subReplies.each(function() { if (checkSubReplyRelated(selfUserId, $(this))) { const subReplyId = $(this).attr('id'); relatedPostIds.push(subReplyId); } }); }); // console.log(relatedPostIds); const component = createJumpComponent(relatedPostIds); const columnEPB = $('#columnEpB') columnEPB.append(component); // Initialize component behavior after DOM insertion initializeComponent(relatedPostIds); })(); // 检查当前回复元素的「贴贴」里边是否包含当前用户id function checkReplyElementRelated(userId, element) { // 先在这个元素内继续选择 `a.item.selected` // const likesGridItem = element.find('a.item.selected'); const innerDiv = element.children().eq(2) // 这个元素里的 title 包含所有 贴贴元素的 HTML 文本,每个 `` 就是一种类型的贴贴 const likesGridItemAs = innerDiv.find('.reply_content').children().eq(1).children(); if (likesGridItemAs.length === 0) { return false; } let found = false; likesGridItemAs.each(function () { const title = $(this).attr('title'); if (checkUserIdInTitle(userId, title)) { found = true; return false; // break loop // ! 这里才是坑,里边有个回调函数,return true 会直接返回到这个回调函数,而不是外层的函数 } }); return found; } function checkUserIdInTitle(userId, title) { const regex = /\/user\/(\w+)/g; let match; while ((match = regex.exec(title)) !== null) { if (match[1] === userId) { return true; } } return false; } // 检查楼中楼(二级回复)是否包含当前用户的贴贴 function checkSubReplyRelated(userId, element) { // 二级回复的贴贴在 .likes_grid 中 const likesGrid = element.find('.likes_grid'); if (likesGrid.length === 0) { return false; } const likesGridItems = likesGrid.find('a.item'); if (likesGridItems.length === 0) { return false; } let found = false; likesGridItems.each(function () { const title = $(this).attr('title'); if (title && checkUserIdInTitle(userId, title)) { found = true; return false; // break loop } }); return found; } function createJumpComponent(postIds) { const isEmpty = postIds.length === 0; // Apple-inspired Bento Grid styling with Tailwind + Custom CSS const styles = ` `; // Generate content based on whether there are posts let contentHtml; let navHtml = ''; if (isEmpty) { contentHtml = `
💭
还没有任何"贴贴标记"
去给感兴趣的回复点个贴贴吧
`; } else { // 添加导航按钮 navHtml = `
`; const linksHtml = postIds.map((postId, index) => `
#${index + 1}
${postId}
`).join(''); contentHtml = `
${linksHtml}
`; } // Complete component HTML const componentHtml = `
贴贴回复 ${!isEmpty ? `${postIds.length}` : ''}
${navHtml} ${contentHtml}
`; return styles + componentHtml; } // Initialize component interactivity function initializeComponent(postIds) { const container = document.getElementById('jumpBentoContainer'); const toggleBtn = document.getElementById('jumpBentoToggle'); if (!container || !toggleBtn) return; let currentIndex = -1; // 当前高亮的索引 // Load saved state const isCollapsed = localStorage.getItem('jumpBentoCollapsed') === 'true'; if (isCollapsed) { container.classList.add('collapsed'); toggleBtn.innerHTML = ` `; } // Toggle collapse/expand toggleBtn.addEventListener('click', () => { const collapsed = container.classList.toggle('collapsed'); localStorage.setItem('jumpBentoCollapsed', collapsed); // Animate icon rotation and change toggleBtn.style.transform = 'rotate(180deg)'; setTimeout(() => { toggleBtn.innerHTML = collapsed ? ` ` : ` `; toggleBtn.style.transform = ''; }, 200); }); if (postIds.length === 0) return; // 空状态不需要导航功能 // 跳转到指定帖子的函数 function jumpToPost(index) { if (index < 0 || index >= postIds.length) return; currentIndex = index; const postId = postIds[index]; const targetElement = document.getElementById(postId); if (targetElement) { targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); // Highlight effect targetElement.style.transition = 'background-color 0.6s ease'; const originalBg = targetElement.style.backgroundColor; targetElement.style.backgroundColor = 'rgba(0, 113, 227, 0.1)'; setTimeout(() => { targetElement.style.backgroundColor = originalBg; }, 1500); // 更新列表中的活跃状态 updateActiveItem(index); updateNavButtons(); } } // 更新列表中的活跃项 function updateActiveItem(index) { const items = document.querySelectorAll('.jump-bento-item'); items.forEach((item, i) => { if (i === index) { item.style.background = 'var(--bento-accent)'; item.style.color = 'white'; item.querySelector('.jump-bento-icon').style.background = 'rgba(255, 255, 255, 0.3)'; } else { item.style.background = ''; item.style.color = ''; item.querySelector('.jump-bento-icon').style.background = ''; } }); } // 更新导航按钮状态 function updateNavButtons() { const prevBtn = document.getElementById('jumpBentoPrev'); const nextBtn = document.getElementById('jumpBentoNext'); if (prevBtn && nextBtn) { prevBtn.disabled = currentIndex <= 0; nextBtn.disabled = currentIndex >= postIds.length - 1; } } // 上一个按钮 const prevBtn = document.getElementById('jumpBentoPrev'); if (prevBtn) { prevBtn.addEventListener('click', () => { if (currentIndex <= 0) { currentIndex = 0; } else { jumpToPost(currentIndex - 1); } }); } // 下一个按钮 const nextBtn = document.getElementById('jumpBentoNext'); if (nextBtn) { nextBtn.addEventListener('click', () => { if (currentIndex < 0) { jumpToPost(0); } else if (currentIndex < postIds.length - 1) { jumpToPost(currentIndex + 1); } }); } // Add smooth scroll behavior to links const links = document.querySelectorAll('.jump-bento-item'); links.forEach((link, index) => { link.addEventListener('click', (e) => { e.preventDefault(); jumpToPost(index); }); }); // 键盘快捷键监听 document.addEventListener('keydown', (e) => { // 检查是否在输入框中 const activeElement = document.activeElement; const isInputFocused = activeElement && ( activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable ); // 如果在输入框中,不触发快捷键 if (isInputFocused) return; // N - 下一个 if (e.key === 'n' || e.key === 'N') { e.preventDefault(); if (currentIndex < 0) { jumpToPost(0); } else if (currentIndex < postIds.length - 1) { jumpToPost(currentIndex + 1); } } // P - 上一个 if (e.key === 'p' || e.key === 'P') { e.preventDefault(); if (currentIndex <= 0) { currentIndex = 0; } else { jumpToPost(currentIndex - 1); } } }); // 初始化按钮状态 updateNavButtons(); }