// ==UserScript== // @name kbin More Improved Collapsible Comments // @namespace http://tampermonkey.net/ // @version 1.3.6-m // @description Improves the comment tree layout and adds a line that lets you collapse replies, and removes the dumb userscript settings menu that malforms the kbin site on mobile browsers. // @author W33D // @match https://kbin.social/* // @match https://fedia.io/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant GM_getValue // @grant GM_setValue // @grant GM.getValue // @grant GM.setValue // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/470454/kbin%20More%20Improved%20Collapsible%20Comments.user.js // @updateURL https://update.greasyfork.icu/scripts/470454/kbin%20More%20Improved%20Collapsible%20Comments.meta.js // ==/UserScript== function toggleReplies(event, comment,expando) { var senderElement = event.target var parent = event.target.parentElement; console.log('Target ' + senderElement.nodeName); console.log('Parent ' + parent.nodeName); const cellText = document.getSelection(); if (cellText.type === 'Range') return; if (parent.nodeName === "BUTTON" || parent.nodeName === "MARKDOWN_TOOLBAR" || parent.nodeName === "FORM" || parent.nodeName.match(/MD/) || parent.className === "more") { return; } if ( senderElement.nodeName === "A" || senderElement.nodeName === "BUTTON" || senderElement.nodeName === "TEXTAREA" || senderElement.className.match(/fa-arrow/) || senderElement.nodeName === "SELECT" || senderElement.nodeName === "OPTION" || senderElement.className === "more") { return; } // Collapse or expand comment if (comment.className.match(/collapsed/)) { // Expand comment comment.classList.remove('collapsed'); // Remove number of children from header let header = comment.querySelector('header'); let numChildrenSpan = header.querySelector('.numChildren'); header.removeChild(numChildrenSpan); // Change + to - let icons = comment.querySelectorAll('.expando-icon'); let icon = icons[icons.length-1]; icon.className = 'expando-icon fas fa-minus'; } else { // Collapse comment comment.classList.add('collapsed'); // Get number of children let children = comment.querySelectorAll('.entry-comment'); let numChildren = children.length; // Add number of children to header let header = comment.querySelector('header'); let numChildrenSpan = document.createElement('span'); numChildrenSpan.className = 'numChildren'; if (numChildren == 1) { numChildrenSpan.innerHTML = '(' + numChildren + ' reply)'; } else { numChildrenSpan.innerHTML = '(' + numChildren + ' replies)'; } header.appendChild(numChildrenSpan); // Get expando icon let icons = comment.querySelectorAll('.expando-icon'); let icon = icons[icons.length-1]; // Change - to + icon.className = 'expando-icon fas fa-plus'; } event.stopPropagation(); } function nestComments(comments,levels) { // Go through comments in reverse order for (let i = comments.length-1; i >= 0; i--) { // Add nested class to comment comments[i].classList.add('nested'); // Add expando to comment let expando = document.createElement('div'); expando.className = 'expando'; // Add line to expando let line = document.createElement('div'); line.className = 'threadLine'; expando.appendChild(line); comments[i].appendChild(expando); // Add + icon to expando let icon = document.createElement('i'); icon.className = 'expando-icon fas fa-minus'; comments[i].appendChild(icon); // Add event listener to comment if (GM_getValue("fulltoggle") == false) { // Add event listener to expando expando.addEventListener('click', function(){toggleReplies(event,comments[i],expando)}); // Add event listener to header let header = comments[i].querySelector('header'); header.addEventListener('click', function(){toggleReplies(event,comments[i],expando)}); //// Add event listener to icon icon.addEventListener('click', function(){toggleReplies(event, comments[i],expando)}); } else { comments[i].addEventListener('click', function(){toggleReplies(event, comments[i], expando)}); } // Check if the previous element is the parent let previousElement = comments[i].previousElementSibling; if (previousElement && previousElement.className.match(/nested/)) { console.log('Found parent'); // Get previous element's children div if it exists let previousChildren = previousElement.querySelector(".children"); if (!previousChildren) { previousChildren = document.createElement('div'); previousChildren.className = 'children'; previousElement.appendChild(previousChildren); } // insert comment into children previousChildren.appendChild(comments[i]); break; } for (let j = i-1; j >= 0; j--) { let comment = comments[i]; let level = levels[i]; let previousComment = comments[j]; let previousLevel = levels[j]; // If previous comment is the parent if (previousLevel == level-1) { // Insert this comment into the parent // Check if parent has a children container let children = previousComment.querySelector('.children'); if (!children) { // If not, create one children = document.createElement('div'); children.className = 'children'; previousComment.appendChild(children); } // Insert comment into children container children.prepend(comment); break; } } } } function applyCommentStyles() { // Add styles to comments let styles = document.createElement('style'); styles.innerHTML = ` .entry-comment { grid-column-gap: 2px; padding: 2px 0 0 0 !important; } .subject .more { bottom: 0px; } .entry-comment header { margin-bottom: 0; } .comment-level--1 { padding-right: 4px !important; padding-bottom: 4px !important; } .comments div { border-left: none !important; } .entry-comment .children { gap: 8px; } .children .entry-comment { border-top: 1px solid var(--kbin-bg) !important; } .comment-level--1 { margin-bottom: 8px; } .author > header .timeago:before { content: 'OP '; color: var(--kbin-danger-color); font-weight: bold; } .collapsed .children, .collapsed .content, .collapsed footer, .collapsed .vote, .collapsed .more { display: none !important; } .entry-comment .children, .entry-comment .content, .entry-comment footer, .entry-comment .vote { opacity: 1; transition: opacity 0.2s ease; } .collapsed { grid-template-areas:"expando-icon avatar header"!important; grid-template-columns: 20px min-content auto!important; grid-template-rows: min-content!important; grid-row-gap: 0!important; } .entry-comment figure, .entry-comment header { transition: margin-left 0.2s ease; } /*.collapsed figure, .collapsed header { margin-left: 24px !important; }*/ .expando { cursor: pointer; grid-area: expando !important; } i.expando-icon { margin: auto; cursor: pointer; } .expando i:before { margin: auto; } .threadLine { background-color: #4a4a4a; transition: background-color 0.2s ease; width: 2px; height: 100%; margin: auto; } .collapsed .threadLine { display: none; } .comment-level--1 .threadLine { background-color: #24262a; } .comment-level--2 .threadLine { background-color: #71ac53; } .comment-level--3 .threadLine { background-color: #ffa500; } .comment-level--4 .threadLine { background-color: #538eac; } .comment-level--5 .threadLine { background-color: #6253ac; } .comment-level--6 .threadLine { background-color: #ac53ac; } .comment-level--7 .threadLine { background-color: #ac5353; } .comment-level--8 .threadLine { background-color: #2b7070; } .comment-level--9 .threadLine { background-color: #b9ab52; } .comment-level--10 .threadLine { background-color: grey; } .expando-icon { grid-area: expando-icon !important; } .expando i:before { color:#4a4a4a; transition: background-color 0.2s ease; } .expando:hover i:before { color:#d7dadc; } .expando:hover .threadLine { background-color: #d7dadc; } .entry-comment > figure { width: 20px; } .entry-comment > figure > a > img, .entry-comment > figure > a > .no-avatar { max-width: 20px!important; max-height: 20px!important; border-radius: 10px; border: 0px transparent !important; } @media (max-width: 992px) { .entry-comment.nested { padding: 2px 0 0 2px !important; grid-column-gap: 0px; grid-template-columns: 14px min-content auto auto; border: 0px; } .comment-level--1 { padding-bottom: 4px !important; } .expando { width: 12px; } .threadLine { width: 2px; } .entry-comment figure a img { height: 16px; width: 16px; } } @media (max-width: 600px) { .entry-comment.nested { padding: 1px !important; grid-column-gap: 1px; grid-row-gap: 0px; grid-template-columns: 14px min-content auto min-content; } .entry-comment { border-bottom: 0px; } } @media (max-width: 1px) { .entry-comment { margin-left: 0 !important; color: red; } } .entry-comment { border-color: transparent !important; grid-template-areas: "expando-icon avatar header vote" "expando body body body" "expando footer footer footer" "expando children children children"; grid-template-columns: 20px min-content auto min-content; grid-template-rows: min-content auto auto; display: grid; margin-left: 0 !important; } .children .entry-comment { margin-left: 0 !important; } .entry-comment > .entry-comment { display: block; } .entry-comment .children { grid-area: children; display: flex; flex-direction: column; } .entry-comment header { cursor: pointer; } `; for (let i = 1; i < 10; i++) { styles.innerHTML += ` blockquote.comment-level--${i} { margin-left: 0 !important; } `; } document.head.append(styles); } function applyToNewPosts() { // Get all comments let comments = document.querySelectorAll(".entry-comment:not(.nested)"); // Get all comment levels let levels = []; for (let i = 0; i < comments.length; i++) { let level = comments[i].className.match(/comment-level--(\d)/)[1]; levels.push(level); } nestComments(comments,levels); } /* function addMenuOption() { var menu = document.querySelectorAll('#header.header menu')[1]; var menuOption = document.createElement('li'); var menuButton = document.createElement('a'); menuButton.className = 'fa-solid fa-user-gear'; menuButton.style = 'cursor: pointer;' menuButton.title = 'Userscript Settings'; menuOption.appendChild(menuButton); menu.appendChild(menuOption); menuButton.addEventListener("click", openConfigMenu); } */ function closeConfigMenu() { document.querySelector('#config-wrapper').remove(); } function openConfigMenu() { console.log('Opening menu'); // Load Config Values // Create div for full-screen background var configWrapper = document.createElement('div'); document.body.appendChild(configWrapper); configWrapper.id = 'config-wrapper'; configWrapper.style = ` width: 100vw; height: 100vh; position: fixed; background-color: #1c1c1c66; top:0; left:0; z-index:99; display: flex; align-items: center; justify-content: center`; // Create div for config var configMenu = document.createElement('div'); configMenu.style = ` padding: 0px 8px 8px 8px; height: 50vh; width: 80vw; border-radius: 1rem; background: var(--kbin-bg); display: grid; grid-template-areas: "header header close" "sidebar options options"; grid-template-columns: auto auto 24px; grid-template-rows: min-content auto; ` configWrapper.appendChild(configMenu); // Create config header /* var configHeader = document.createElement('h3'); configHeader.style = "margin: 0;"; configHeader.innerHTML = "Userscript Settings"; */ // Create sidebar var configSidebar = document.createElement('ul'); configSidebar.style = "grid-area: sidebar; list-style: none; padding-inline-start:0"; configMenu.appendChild(configSidebar); // configMenu.appendChild(configHeader); var configOptions = document.createElement('div'); configOptions.style = 'grid-area: options; overflow-y: scroll'; configMenu.appendChild(configOptions); addUserscriptOption(configSidebar, configOptions, "kicc", "Improved Collapsible Comments"); addConfigOption(configOptions, "fulltoggle", "Toggle comments by clicking or tapping anywhere on them"); var userscriptName = document.createElement('a'); var fullCommentToggle = document.createElement('div'); configOptions.appendChild(fullCommentToggle); var closeButton = document.createElement('div'); closeButton.innerHTML = ``; closeButton.style = 'grid-area: close; padding: 4px;'; configMenu.appendChild(closeButton); closeButton.addEventListener("click", closeConfigMenu) } function addUserscriptOption(sidebar,options,userscript,title) { var sidebarLink = document.createElement('li'); sidebarLink.innerHTML = `${title}`; sidebar.appendChild(sidebarLink); var optionsHeader = document.createElement('h4'); optionsHeader.id = userscript; optionsHeader.innerHTML = title; options.appendChild(optionsHeader); } async function addConfigOption(options,name,title) { var option = document.createElement('div'); option.innerHTML = `${title}` options.appendChild(option); option.addEventListener('click', function(){saveConfigOption(option,name)}) } async function saveConfigOption(option,name) { var value = document.getElementById(name); await GM.setValue(name, value.checked); console.log(await GM.getValue(name)); } (async function () { // addMenuOption(); applyToNewPosts(); applyCommentStyles(); // Observe for new posts let observer = new MutationObserver(applyToNewPosts); observer.observe(document.body, { childList: true, subtree: true }); console.log(await GM.getValue("fulltoggle")); })();