您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Remove clutter in the comments view
// ==UserScript== // @name Refined GitHub Comments // @license MIT // @homepageURL https://github.com/bluwy/refined-github-comments // @supportURL https://github.com/bluwy/refined-github-comments // @namespace https://gf.qytechs.cn/en/scripts/465056-refined-github-comments // @version 0.2.2 // @description Remove clutter in the comments view // @author Bjorn Lu // @match https://github.com/** // @icon https://www.google.com/s2/favicons?sz=64&domain=github.com // @grant none // ==/UserScript== // #region User settings // common bots that i already know what they do const authorsToMinimize = [ 'changeset-bot', 'codeflowapp', 'netlify', 'vercel', 'pkg-pr-new', 'codecov', 'astrobot-houston', 'codspeed-hq', ] // common comments that don't really add value const commentMatchToMinimize = [ /^![a-z]/, // commands that start with ! /^\/[a-z]/, // commands that start with / /^> [email protected]/, // astro preview release bot ] // #endregion // #region Run code // Used by `minimizeDiscussionThread` let expandedThread = false const maxParentThreadHeight = 185 ;(function () { 'use strict' run() // listen to github page loaded event document.addEventListener('pjax:end', () => run()) document.addEventListener('turbo:render', () => run()) })() function run() { // Comments view const allTimelineItem = document.querySelectorAll('.js-timeline-item') const seenComments = [] allTimelineItem.forEach((timelineItem) => { minimizeComment(timelineItem) minimizeBlockquote(timelineItem, seenComments) }) // Discussion threads view if (location.pathname.includes('/discussions/')) { minimizeDiscussionThread() } } // #endregion // #region Features: minimize comment // test urls: // https://github.com/withastro/astro/pull/6845 /** * @param {HTMLElement} timelineItem */ function minimizeComment(timelineItem) { // things can happen twice in github for some reason if (timelineItem.querySelector('.refined-github-comments-toggle')) return const header = timelineItem.querySelector('.timeline-comment-header') if (!header) return const headerName = header.querySelector('a.author') if (!headerName) return const commentBody = timelineItem.querySelector('.comment-body') if (!commentBody) return const commentBodyText = commentBody.innerText.trim() // minimize the comment if ( authorsToMinimize.includes(headerName.innerText) || commentMatchToMinimize.some((match) => match.test(commentBodyText)) ) { const commentContent = timelineItem.querySelector('.edit-comment-hide') if (!commentContent) return const commentActions = timelineItem.querySelector( '.timeline-comment-actions' ) if (!commentActions) return const headerH3 = header.querySelector('h3') if (!headerH3) return const headerDiv = headerH3.querySelector('div') if (!headerDiv) return // hide comment header.style.borderBottom = 'none' commentContent.style.display = 'none' // add comment excerpt const excerpt = document.createElement('span') excerpt.setAttribute( 'class', 'text-fg-muted text-normal text-italic css-truncate css-truncate-overflow mr-2' ) excerpt.innerHTML = commentBodyText.slice(0, 100) excerpt.style.opacity = '0.5' headerH3.classList.add('css-truncate', 'css-truncate-overflow') headerDiv.appendChild(excerpt) // add toggle button const toggleBtn = toggleComment((isShow) => { // headerH3 class needs to be toggled too so that the "edited dropdown" can be toggled if (isShow) { headerH3.classList.remove('css-truncate', 'css-truncate-overflow') header.style.borderBottom = '' commentContent.style.display = '' excerpt.style.display = 'none' } else { headerH3.classList.add('css-truncate', 'css-truncate-overflow') header.style.borderBottom = 'none' commentContent.style.display = 'none' excerpt.style.display = '' } }) commentActions.prepend(toggleBtn) } } // #endregion // #region Features: minimize blockquote // test urls: // https://github.com/bluwy/refined-github-comments/issues/1 // https://github.com/sveltejs/svelte/issues/2323 // https://github.com/pnpm/pnpm/issues/6463 /** * @param {HTMLElement} timelineItem * @param {{ text: string, id: string, author: string }[]} seenComments */ function minimizeBlockquote(timelineItem, seenComments) { const commentBody = timelineItem.querySelector('.comment-body') if (!commentBody) return const commentId = timelineItem.querySelector('.timeline-comment-group')?.id if (!commentId) return const commentAuthor = timelineItem.querySelector( '.timeline-comment-header a.author' )?.innerText if (!commentAuthor) return const commentText = commentBody.innerText.trim().replace(/\s+/g, ' ') // bail early in first comment and if comment is already checked before if ( seenComments.length === 0 || commentBody.querySelector('.refined-github-comments-reply-text') ) { seenComments.push({ text: commentText, id: commentId, author: commentAuthor, }) return } const blockquotes = commentBody.querySelectorAll(':scope > blockquote') for (const blockquote of blockquotes) { const blockquoteText = blockquote.innerText.trim().replace(/\s+/g, ' ') const dupIndex = seenComments.findIndex( (comment) => comment.text === blockquoteText ) if (dupIndex >= 0) { const dup = seenComments[dupIndex] // if replying to the one above, always minimize it if (dupIndex === seenComments.length - 1) { // use span.js-clear so github would remove this summary when re-quoting this reply, // add nbsp so that the summary tag has some content, that the details would also // get copied when re-quoting too. const summary = `\ <span class="js-clear text-italic refined-github-comments-reply-text"> Replying to <strong>@${dup.author}</strong> above </span> ` blockquote.innerHTML = `<details><summary>${summary}</summary>${blockquote.innerHTML}</details>` } // if replying to a long comment, or a comment with code, always minimize it else if (blockquoteText.length > 200 || blockquote.querySelector('pre')) { // use span.js-clear so github would remove this summary when re-quoting this reply, // add nbsp so that the summary tag has some content, that the details would also // get copied when re-quoting too. const summary = `\ <span class="js-clear text-italic refined-github-comments-reply-text"> Replying to <strong>@${dup.author}</strong>'s <a href="#${dup.id}">comment</a> </span> ` blockquote.innerHTML = `<details><summary>${summary}</summary>${blockquote.innerHTML}</details>` } // otherwise, just add a hint so we don't have to navigate away a short sentence else { // use span.js-clear so github would remove this hint when re-quoting this reply const hint = `\ <span dir="auto" class="js-clear text-italic refined-github-comments-reply-text" style="display: block; margin-top: -0.5rem; opacity: 0.7; font-size: 90%;"> — <strong>@${dup.author}</strong> said in <a href="#${dup.id}">comment</a> </span>` blockquote.insertAdjacentHTML('beforeend', hint) } continue } const partialDupIndex = seenComments.findIndex((comment) => comment.text.includes(blockquoteText) ) if (partialDupIndex >= 0) { const dup = seenComments[partialDupIndex] // get first four words and last four words, craft a text fragment to highlight const splitted = blockquoteText.split(' ') const textFragment = splitted.length < 9 ? `#:~:text=${encodeURIComponent(blockquoteText)}` : `#:~:text=${encodeURIComponent( splitted.slice(0, 4).join(' ') )},${encodeURIComponent(splitted.slice(-4).join(' '))}` // if replying to the one above, prepend hint if (partialDupIndex === seenComments.length - 1) { // use span.js-clear so github would remove this hint when re-quoting this reply const hint = `\ <span dir="auto" class="js-clear text-italic refined-github-comments-reply-text" style="display: block; margin-top: -0.5rem; opacity: 0.7; font-size: 90%;"> — <strong>@${dup.author}</strong> <a href="${textFragment}">said</a> above </span>` blockquote.insertAdjacentHTML('beforeend', hint) } // prepend generic hint else { // use span.js-clear so github would remove this hint when re-quoting this reply const hint = `\ <span dir="auto" class="js-clear text-italic refined-github-comments-reply-text" style="display: block; margin-top: -0.5rem; opacity: 0.7; font-size: 90%;"> — <strong>@${dup.author}</strong> <a href="${textFragment}">said</a> in <a href="#${dup.id}">comment</a> </span>` blockquote.insertAdjacentHTML('beforeend', hint) } } } seenComments.push({ text: commentText, id: commentId, author: commentAuthor }) } // #endregion // #region Features: minimize discussion threads // test urls: // https://github.com/vitejs/vite/discussions/18191 function minimizeDiscussionThread() { if (expandedThread) { _minimizeDiscussionThread() return } const discussionContainer = document.querySelector( '.discussion.js-discussion > .js-timeline-marker' ) if (!discussionContainer) return const tripleDotMenuContainer = document.querySelector( '.timeline-comment-actions' ) if (!tripleDotMenuContainer) return // Skip if already added if (document.getElementById('refined-github-comments-expand-btn') != null) return tripleDotMenuContainer.style.display = 'flex' tripleDotMenuContainer.style.alignItems = 'center' // Create a "Collapse threads" button to enable this feature const expandBtn = document.createElement('button') expandBtn.id = 'refined-github-comments-expand-btn' expandBtn.setAttribute( 'class', 'Button--iconOnly Button--invisible Button--medium Button mr-2' ) expandBtn.innerHTML = `\ <svg class="Button-visual octicon octicon-zap" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"> <path d="M9.504.43a1.516 1.516 0 0 1 2.437 1.713L10.415 5.5h2.123c1.57 0 2.346 1.909 1.22 3.004l-7.34 7.142a1.249 1.249 0 0 1-.871.354h-.302a1.25 1.25 0 0 1-1.157-1.723L5.633 10.5H3.462c-1.57 0-2.346-1.909-1.22-3.004L9.503.429Zm1.047 1.074L3.286 8.571A.25.25 0 0 0 3.462 9H6.75a.75.75 0 0 1 .694 1.034l-1.713 4.188 6.982-6.793A.25.25 0 0 0 12.538 7H9.25a.75.75 0 0 1-.683-1.06l2.008-4.418.003-.006a.036.036 0 0 0-.004-.009l-.006-.006-.008-.001c-.003 0-.006.002-.009.004Z"></path> </svg> ` expandBtn.title = 'Collapse threads' expandBtn.addEventListener('click', () => { expandedThread = true _minimizeDiscussionThread() expandBtn.remove() }) tripleDotMenuContainer.prepend(expandBtn) } function _minimizeDiscussionThread() { const timelineComments = document.querySelectorAll( '.timeline-comment.comment:not(.nested-discussion-timeline-comment)' ) for (const timelineComment of timelineComments) { // Skip if already handled if (timelineComment.querySelector('.refined-github-comments-toggle')) continue const parentThreadContent = timelineComment.children[1] if (!parentThreadContent) continue // Find the "N replies" bottom text (a bit finicky but seems like the best selector) const bottomText = parentThreadContent.querySelector( 'span.color-fg-muted.no-wrap' ) const childrenThread = timelineComment.querySelector( '[data-child-comments]' ) // Skip if 0 replies if ( bottomText && childrenThread && /\d+/.exec(bottomText.textContent)?.[0] !== '0' ) { // Prepend a "expand thread" button // const expandBtn = document.createElement('button') // expandBtn.setAttribute('class', 'Button--secondary Button--small Button') // expandBtn.innerHTML = 'Expand thread' // expandBtn.addEventListener('click', () => { // threadComment.style.display = '' // bottomText.style.display = 'none' // }) const toggleBtn = toggleComment((isShow) => { // Re-query as GitHub may update it when , e.g. showing more comments const childrenThreadAgain = timelineComment.querySelector( '[data-child-comments]' ) if (childrenThreadAgain) { if (isShow) { childrenThreadAgain.style.display = '' bottomText.classList.add('color-fg-muted') } else { childrenThreadAgain.style.display = 'none' bottomText.classList.remove('color-fg-muted') } } }) bottomText.parentElement.insertBefore(toggleBtn, bottomText) childrenThread.style.display = 'none' bottomText.classList.remove('color-fg-muted') // Lazy to make the bottom text a button, share the click event to the button for now // NOTE: This click happens to expand the comment too. I'm not sure how to prevent that. bottomText.addEventListener('click', () => { toggleBtn.click() }) } const commentBody = parentThreadContent.querySelector('.comment-body') if (commentBody && commentBody.clientHeight > maxParentThreadHeight) { // Shrink the OP thread to max height const css = `max-height:${maxParentThreadHeight}px;mask-image:linear-gradient(180deg, #000 80%, transparent);-webkit-mask-image:linear-gradient(180deg, #000 80%, transparent);` commentBody.style.cssText += css // Add "view" const commentActions = timelineComment.querySelector( '.timeline-comment-actions' ) const toggleCommentBodyBtn = toggleComment((isShow) => { if (isShow) { commentBody.style.maxHeight = '' commentBody.style.maskImage = '' commentBody.style.webkitMaskImage = '' } else { commentBody.style.cssText += css } }) commentActions.style.display = 'flex' commentActions.style.alignItems = 'center' commentActions.prepend(toggleCommentBodyBtn) // Auto-expand on first click for nicer UX commentBody.addEventListener('click', () => { if (toggleCommentBodyBtn.dataset.show === 'false') { toggleCommentBodyBtn.click() } }) } } } // #endregion // #region Utilities // create the toggle comment like github does when you hide a comment function toggleComment(onClick) { const btn = document.createElement('button') // copied from github hidden comment style btn.innerHTML = ` <div class="color-fg-muted f6 no-wrap"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-unfold position-relative"> <path d="m8.177.677 2.896 2.896a.25.25 0 0 1-.177.427H8.75v1.25a.75.75 0 0 1-1.5 0V4H5.104a.25.25 0 0 1-.177-.427L7.823.677a.25.25 0 0 1 .354 0ZM7.25 10.75a.75.75 0 0 1 1.5 0V12h2.146a.25.25 0 0 1 .177.427l-2.896 2.896a.25.25 0 0 1-.354 0l-2.896-2.896A.25.25 0 0 1 5.104 12H7.25v-1.25Zm-5-2a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM6 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 6 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM12 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 12 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5Z"></path> </svg> </div> <div class="color-fg-muted f6 no-wrap" style="display: none"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-fold position-relative"> <path d="M10.896 2H8.75V.75a.75.75 0 0 0-1.5 0V2H5.104a.25.25 0 0 0-.177.427l2.896 2.896a.25.25 0 0 0 .354 0l2.896-2.896A.25.25 0 0 0 10.896 2ZM8.75 15.25a.75.75 0 0 1-1.5 0V14H5.104a.25.25 0 0 1-.177-.427l2.896-2.896a.25.25 0 0 1 .354 0l2.896 2.896a.25.25 0 0 1-.177.427H8.75v1.25Zm-6.5-6.5a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM6 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 6 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM12 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 12 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5Z"></path> </svg> </div> ` const showNode = btn.querySelector('div:nth-child(1)') const hideNode = btn.querySelector('div:nth-child(2)') let isShow = false btn.setAttribute('type', 'button') btn.setAttribute( 'class', 'refined-github-comments-toggle timeline-comment-action btn-link' ) btn.dataset.show = isShow btn.addEventListener('click', () => { isShow = !isShow btn.dataset.show = isShow if (isShow) { showNode.style.display = 'none' hideNode.style.display = '' } else { showNode.style.display = '' hideNode.style.display = 'none' } onClick(isShow) }) return btn } // #endregion
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址