// ==UserScript==
// @name Refined GitHub Comments (TJ)
// @license MIT
// @homepageURL https://github.com/tjx666/user-scripts
// @supportURL https://github.com/tjx666/user-scripts/issues
// @namespace https://github.com/tjx666/user-scripts
// @version 0.4.3
// @description Remove clutter in the comments view
// @author YuTengjing
// @match https://github.com/*/issues/*
// @match https://github.com/*/pull/*
// @match https://github.com/*/discussions/*
// @match https://github.com/*/commits/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=github.com
// @grant none
// ==/UserScript==
// common bots that i already know what they do
const authorsToMinimize = [
'changeset-bot',
'codeflowapp',
'netlify',
// 'vercel',
'pkg-pr-new',
'codecov',
'astrobot-houston',
'codspeed-hq',
'lobehubbot',
];
// 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
/^👍[\s\S]*Thank you for raising your pull request/, // lobehubbot PR thanks
/^👀[\s\S]*Thank you for raising an issue/, // lobehubbot issue thanks
/^✅[\s\S]*This issue is closed/, // lobehubbot issue closed
/^❤️[\s\S]*Great PR/, // lobehubbot PR merged thanks
/Bot detected the issue body's language is not English/, // lobehubbot translation
];
// DOM selectors
const SELECTORS = {
TIMELINE_ELEMENT: '.LayoutHelpers-module__timelineElement--IsjVR, [data-wrapper-timeline-id]',
COMMENT_BODY:
'[data-testid="markdown-body"] .markdown-body, .IssueCommentViewer-module__IssueCommentBody--xvkt3 .markdown-body',
COMMENT_CONTENT:
'.IssueCommentViewer-module__IssueCommentBody--xvkt3, [data-testid="markdown-body"]',
COMMENT_HEADER: '[data-testid="comment-header"]',
AUTHOR_LINK: '.ActivityHeader-module__AuthorLink--D7Ojk, [data-testid="avatar-link"]',
COMMENT_ACTIONS:
'[data-testid="comment-header-hamburger"], .CommentActions-module__CommentActionsIconButton--EOXv7',
TITLE_CONTAINER: '.ActivityHeader-module__TitleContainer--pa99A',
FOOTER_CONTAINER:
'.ActivityHeader-module__footer--ssKOW, .ActivityHeader-module__FooterContainer--FHEpM',
ACTIONS_CONTAINER: '.ActivityHeader-module__ActionsButtonsContainer--L7GUK',
};
// Used by `minimizeDiscussionThread`
let expandedThread = false;
const maxParentThreadHeight = 185;
// Used by `minimizeReactBlockquote` to track seen comments
const seenReactComments = [];
(function () {
'use strict';
run();
// listen to github page loaded event
document.addEventListener('pjax:end', () => run());
document.addEventListener('turbo:render', () => run());
})();
function run() {
injectCSS();
setTimeout(() => {
// Handle React version comments
const reactComments = document.querySelectorAll('.react-issue-comment');
reactComments.forEach((comment) => {
minimizeReactComment(comment);
minimizeReactBlockquote(comment, seenReactComments);
});
// Handle PR comments
const timelineItems = document.querySelectorAll('.js-timeline-item');
timelineItems.forEach((timelineItem) => {
minimizePRComment(timelineItem);
});
// Discussion threads view
if (location.pathname.includes('/discussions/')) {
minimizeDiscussionThread();
}
setupDOMObserver();
}, 1000);
}
function injectCSS() {
// Remove existing style if any
const existingStyle = document.getElementById('refined-github-comments-style');
if (existingStyle) {
existingStyle.remove();
}
const style = document.createElement('style');
style.id = 'refined-github-comments-style';
style.textContent = `
/* Layout for minimized React comments */
.refined-github-comments-minimized .ActivityHeader-module__CommentHeaderContentContainer--OOrIN {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
flex-wrap: nowrap !important;
gap: 4px !important;
flex: 1 !important;
}
.refined-github-comments-minimized .ActivityHeader-module__FooterContainer--FHEpM {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
flex: 1 !important;
overflow: hidden !important;
}
.refined-github-comments-minimized .ActivityHeader-module__narrowViewportWrapper--k4ncm.ActivityHeader-module__ActionsContainer--Ebsux {
flex-grow: 0 !important;
}
.refined-github-comments-minimized .ActivityHeader-module__HeaderMutedText--aJAo0 {
flex-shrink: 0 !important;
}
/* Excerpt text styling */
.refined-github-comments-minimized .ActivityHeader-module__FooterContainer--FHEpM .color-fg-muted,
.timeline-comment-header .css-truncate-overflow {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
display: inline-block !important;
}
/* Toggle button styling */
.refined-github-comments-toggle.timeline-comment-action {
padding: 0 6px !important;
margin: 0 !important;
}
/* Hidden elements */
.refined-github-comments-hidden {
display: none !important;
}
`;
document.head.appendChild(style);
}
function setupDOMObserver() {
const observer = new MutationObserver((mutations) => {
const hasNewComments = mutations.some(
(mutation) =>
mutation.type === 'childList' &&
Array.from(mutation.addedNodes).some(
(node) =>
node.nodeType === Node.ELEMENT_NODE &&
(node.classList?.contains('react-issue-comment') ||
node.querySelector?.('.react-issue-comment') ||
node.classList?.contains('js-timeline-item') ||
node.querySelector?.('.js-timeline-item')),
),
);
if (hasNewComments) {
setTimeout(() => {
// Handle React comments
document.querySelectorAll('.react-issue-comment').forEach((comment) => {
minimizeReactComment(comment);
minimizeReactBlockquote(comment, seenReactComments);
});
// Handle PR comments
document.querySelectorAll('.js-timeline-item').forEach((timelineItem) => {
minimizePRComment(timelineItem);
});
}, 500);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
/**
* Extract author name from link
* @param {HTMLElement} authorLink
* @returns {string}
*/
function getAuthorName(authorLink) {
let authorName =
authorLink.getAttribute('href')?.replace('/', '') || authorLink.textContent.trim();
if (authorName.startsWith('apps/')) {
authorName = authorName.replace('apps/', '');
}
return authorName;
}
/**
* Check if comment should be minimized
* @param {string} authorName
* @param {string} commentText
* @returns {boolean}
*/
function shouldMinimizeComment(authorName, commentText) {
const shouldMinimizeByAuthor = authorsToMinimize.includes(authorName);
const matchingPattern = commentMatchToMinimize.find((match) => match.test(commentText));
return shouldMinimizeByAuthor || matchingPattern;
}
/**
* Create comment excerpt element
* @param {string} text
* @returns {HTMLElement}
*/
function createExcerpt(text) {
const excerpt = document.createElement('span');
excerpt.className = 'css-truncate-overflow text-fg-muted text-italic';
excerpt.style.fontSize = '12px';
excerpt.style.opacity = '0.6';
excerpt.style.whiteSpace = 'nowrap';
excerpt.style.overflow = 'hidden';
excerpt.style.textOverflow = 'ellipsis';
excerpt.style.display = 'inline-block';
excerpt.style.verticalAlign = 'middle';
excerpt.textContent = text;
return excerpt;
}
/**
* Setup toggle button styling and placement
* @param {HTMLElement} commentActions
* @param {HTMLElement} toggleBtn
* @param {HTMLElement} beforeElement - Optional element to insert before
*/
function setupToggleButton(commentActions, toggleBtn, beforeElement = null) {
commentActions.style.display = 'flex';
commentActions.style.alignItems = 'center';
commentActions.style.gap = '4px';
if (beforeElement) {
commentActions.insertBefore(toggleBtn, beforeElement);
} else {
commentActions.insertBefore(toggleBtn, commentActions.firstChild);
}
}
/**
* Handle PR comments
* @param {HTMLElement} timelineItem
*/
function minimizePRComment(timelineItem) {
// Skip if already processed
if (timelineItem.querySelector('.refined-github-comments-toggle')) {
return;
}
// Find timeline comment
const timelineComment = timelineItem.querySelector('.timeline-comment');
if (!timelineComment) return;
// Find comment header
const header = timelineComment.querySelector('.timeline-comment-header');
if (!header) return;
// Find author in h3 strong a structure
const authorLink = header.querySelector('h3 strong .author');
if (!authorLink) return;
// Find comment body
const commentBody = timelineComment.querySelector(
'.comment-body.markdown-body.js-comment-body',
);
if (!commentBody) return;
const authorName = getAuthorName(authorLink);
const commentBodyText = commentBody.innerText.trim();
if (shouldMinimizeComment(authorName, commentBodyText)) {
// Find comment actions container
const commentActions = header.querySelector('.timeline-comment-actions');
if (!commentActions) return;
// Hide comment body content
const taskLists = timelineComment.querySelector('task-lists');
if (taskLists) {
taskLists.style.display = 'none';
} else {
const commentBody = timelineComment.querySelector('.comment-body');
if (commentBody) {
commentBody.style.display = 'none';
}
}
// Remove border bottom from header
header.style.borderBottom = 'none';
// Hide mention buttons
toggleMentionButtons(timelineItem, false);
// Add comment excerpt in header
const titleContainer = header.querySelector('h3.f5.text-normal');
if (titleContainer) {
// Find the div inside h3 to add excerpt there (keep it on same line)
const innerDiv = titleContainer.querySelector('div');
if (innerDiv) {
const excerpt = createExcerpt(commentBodyText);
innerDiv.parentElement.style.overflow = 'hidden';
innerDiv.style.display = 'flex';
innerDiv.style.alignItems = 'center';
innerDiv.style.gap = '4px';
excerpt.style.flex = '1';
innerDiv.appendChild(excerpt);
}
// Add toggle button
const toggleBtn = toggleComment((isShow) => {
const currentTaskLists = timelineComment.querySelector('task-lists');
const currentCommentBody = timelineComment.querySelector('.comment-body');
if (isShow) {
if (currentTaskLists) {
currentTaskLists.style.display = '';
} else if (currentCommentBody) {
currentCommentBody.style.display = '';
}
header.style.borderBottom = '';
if (innerDiv && innerDiv.querySelector('.css-truncate-overflow')) {
innerDiv.querySelector('.css-truncate-overflow').style.display = 'none';
}
toggleMentionButtons(timelineItem, true);
} else {
if (currentTaskLists) {
currentTaskLists.style.display = 'none';
} else if (currentCommentBody) {
currentCommentBody.style.display = 'none';
}
header.style.borderBottom = 'none';
if (innerDiv && innerDiv.querySelector('.css-truncate-overflow')) {
innerDiv.querySelector('.css-truncate-overflow').style.display = '';
}
toggleMentionButtons(timelineItem, false);
}
});
// Style and insert toggle button
setupToggleButton(commentActions, toggleBtn);
}
}
}
/**
* Toggle mention buttons visibility
* @param {HTMLElement} element - Can be either a React comment or PR timeline item
* @param {boolean} show
*/
function toggleMentionButtons(element, show) {
let mentionContainer = null;
// Strategy 1: Find mention container directly within the element (for PR comments)
mentionContainer = element.querySelector('.avatar-parent-child');
// Strategy 2: Find via closest timeline element (for React comments)
if (!mentionContainer) {
const timelineElement = element.closest(SELECTORS.TIMELINE_ELEMENT);
if (timelineElement) {
mentionContainer = timelineElement.querySelector('.avatar-parent-child');
}
}
// Strategy 3: For issue comments, try to find timeline element as sibling container
if (!mentionContainer) {
// Look for timeline element that contains both avatar-parent-child and this element
const timelineElements = document.querySelectorAll(SELECTORS.TIMELINE_ELEMENT);
for (const timeline of timelineElements) {
if (timeline.contains(element) && timeline.querySelector('.avatar-parent-child')) {
mentionContainer = timeline.querySelector('.avatar-parent-child');
break;
}
}
}
// Strategy 4: Direct search for mention buttons in nearby containers
if (!mentionContainer) {
// Look for mention buttons in the document that might be related to this comment
const commentId = element.querySelector('[data-testid="comment-header"]')?.id;
if (commentId) {
const timelineWrapper = document.querySelector(
`[data-wrapper-timeline-id="${commentId}"]`,
);
if (timelineWrapper) {
mentionContainer = timelineWrapper.querySelector('.avatar-parent-child');
}
}
}
if (!mentionContainer) return;
const mentionBtns = mentionContainer.querySelectorAll('.rgh-quick-mention');
mentionBtns.forEach((btn) => {
if (show) {
btn.classList.remove('refined-github-comments-hidden');
} else {
btn.classList.add('refined-github-comments-hidden');
}
});
}
/**
* Handle React version GitHub comments
* @param {HTMLElement} reactComment
*/
function minimizeReactComment(reactComment) {
// Skip if already processed
if (reactComment.querySelector('.refined-github-comments-toggle')) {
return;
}
// Find comment header
const header = reactComment.querySelector(SELECTORS.COMMENT_HEADER);
if (!header) return;
// Find author
const authorLink = header.querySelector(SELECTORS.AUTHOR_LINK);
if (!authorLink) return;
// Find comment body
const commentBody = reactComment.querySelector(SELECTORS.COMMENT_BODY);
if (!commentBody) return;
const authorName = getAuthorName(authorLink);
const commentBodyText = commentBody.innerText.trim();
if (shouldMinimizeComment(authorName, commentBodyText)) {
const commentContent = reactComment.querySelector(SELECTORS.COMMENT_CONTENT);
if (!commentContent) return;
const commentActions = header.querySelector(SELECTORS.COMMENT_ACTIONS);
if (!commentActions) return;
const titleContainer = header.querySelector(SELECTORS.TITLE_CONTAINER);
if (!titleContainer) return;
// Hide comment content
commentContent.style.display = 'none';
// Remove border bottom from header
header.style.borderBottom = 'none';
// Hide mention buttons
toggleMentionButtons(reactComment, false);
// Add CSS class for layout styling
reactComment.classList.add('refined-github-comments-minimized');
// Add comment excerpt
const footerContainer = header.querySelector(SELECTORS.FOOTER_CONTAINER);
let excerpt = null;
if (footerContainer) {
excerpt = document.createElement('span');
excerpt.setAttribute('class', 'color-fg-muted text-italic');
excerpt.innerHTML = commentBodyText;
excerpt.style.opacity = '0.5';
excerpt.style.fontSize = '12px';
excerpt.style.marginLeft = '4px';
footerContainer.appendChild(excerpt);
}
// Add toggle button
const toggleBtn = toggleComment((isShow) => {
if (isShow) {
commentContent.style.display = '';
header.style.borderBottom = '';
if (excerpt) excerpt.style.display = 'none';
toggleMentionButtons(reactComment, true);
reactComment.classList.remove('refined-github-comments-minimized');
} else {
commentContent.style.display = 'none';
header.style.borderBottom = 'none';
if (excerpt) excerpt.style.display = '';
toggleMentionButtons(reactComment, false);
reactComment.classList.add('refined-github-comments-minimized');
}
});
// Find actions container and setup toggle button
const actionsContainer = header.querySelector(SELECTORS.ACTIONS_CONTAINER);
if (!actionsContainer) return;
setupToggleButton(actionsContainer, toggleBtn, commentActions);
}
}
/**
* Handle blockquotes in React comments (new GitHub structure)
* @param {HTMLElement} reactComment
* @param {{ text: string, id: string, author: string }[]} seenComments
*/
function minimizeReactBlockquote(reactComment, seenComments) {
const commentBody = reactComment.querySelector('[data-testid="markdown-body"] .markdown-body');
if (!commentBody) return;
const commentHeader = reactComment.querySelector('[data-testid="comment-header"]');
if (!commentHeader) return;
const commentId = commentHeader.id; // e.g., "issuecomment-1528936387"
if (!commentId) return;
const authorLink = commentHeader.querySelector('[data-testid="avatar-link"]');
if (!authorLink) return;
const commentAuthor = authorLink.textContent.trim();
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) {
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')) {
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 {
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) {
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 {
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 });
}
// test urls:
// https://github.com/vitejs/vite/discussions/18191
function minimizeDiscussionThread() {
if (expandedThread) {
_minimizeDiscussionThread();
return;
}
// Look for the first main timeline comment in discussions
const firstMainComment = document.querySelector(
'.timeline-comment:not(.nested-discussion-timeline-comment)',
);
if (!firstMainComment) return;
const tripleDotMenuContainer = firstMainComment.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 Button--iconOnly Button--invisible Button--medium 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() {
// Find all main timeline comments (not nested replies)
const timelineComments = document.querySelectorAll(
'.timeline-comment:not(.nested-discussion-timeline-comment)',
);
for (const timelineComment of timelineComments) {
// Skip if already handled
if (timelineComment.querySelector('.refined-github-comments-toggle')) continue;
// Look for child comments container based on comment ID
// Find the permalink link which contains the comment ID
const commentPermalink = timelineComment.querySelector('a[href^="#discussioncomment-"]');
let childCommentsContainer = null;
if (commentPermalink) {
const href = commentPermalink.getAttribute('href'); // e.g., "#discussioncomment-10741444"
const commentId = href.substring(1); // Remove the # to get "discussioncomment-10741444"
childCommentsContainer = document.querySelector(`#child-comments-${commentId}`);
}
// Fallback: look for any child comments container near this comment
if (!childCommentsContainer) {
// Check if there's a child comments container right after this comment
const parentContainer = timelineComment.closest('.d-flex');
if (parentContainer && parentContainer.parentElement) {
const possibleChildContainer =
parentContainer.parentElement.querySelector('[data-child-comments]');
if (possibleChildContainer) {
childCommentsContainer = possibleChildContainer;
}
}
}
// Find replies count text (e.g. "4 replies")
const repliesTextElement = timelineComment.querySelector(
'.f6.mr-3 .color-fg-muted.no-wrap',
);
if (
childCommentsContainer &&
repliesTextElement &&
repliesTextElement.textContent.includes('replies')
) {
const repliesCount = parseInt(
repliesTextElement.textContent.match(/(\d+)\s+replies?/)?.[1] || '0',
);
// Skip if 0 replies
if (repliesCount > 0) {
// Find the parent element to insert toggle button
const repliesContainer = repliesTextElement.parentElement;
if (repliesContainer) {
const toggleBtn = toggleComment((isShow) => {
if (isShow) {
childCommentsContainer.style.display = '';
repliesTextElement.classList.add('color-fg-muted');
} else {
childCommentsContainer.style.display = 'none';
repliesTextElement.classList.remove('color-fg-muted');
}
});
repliesContainer.insertBefore(toggleBtn, repliesTextElement);
childCommentsContainer.style.display = 'none';
repliesTextElement.classList.remove('color-fg-muted');
// Make replies text clickable too
repliesTextElement.style.cursor = 'pointer';
repliesTextElement.addEventListener('click', () => {
toggleBtn.click();
});
}
}
}
// Handle long comment bodies
const commentBody = timelineComment.querySelector(
'.comment-body.markdown-body.js-comment-body',
);
if (commentBody && commentBody.clientHeight > maxParentThreadHeight) {
// Apply height limit and mask
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 toggle button for comment body
const commentActions = timelineComment.querySelector('.timeline-comment-actions');
if (commentActions) {
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.style.cursor = 'pointer';
commentBody.addEventListener('click', () => {
if (toggleCommentBodyBtn.dataset.show === 'false') {
toggleCommentBodyBtn.click();
}
});
}
}
}
}
// 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;
}