kbin enhancement script

Few small changes to the kbin UI while they still develop some features

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name     kbin enhancement script
// @description Few small changes to the kbin UI while they still develop some features
// @namespace com.sirpsychomantis
// @license MIT
// @version  1.11
// @grant    none
// @run-at document-end
// @match  https://fedia.io/*
// @match  https://kbin.social/*
// ==/UserScript==


(function(){
  const version = "1.9";
  
  const style = document.createElement('style');
  style.textContent = `
    .entry figure {overflow: hidden}
    .comment .badge {padding:.25rem;margin-left:5px}
    #kes-version-dialog {position: fixed; width: 100vw; height: 100vh; top: 0; left: 0; display: flex; align-items: center; justify-content: center; background-color: rgba(0,0,0,.3); z-index: 9999999}
    .kes-version-dialog-content {background: #ddd; color: #444; position: relative; padding: 40px}
		.kes-expand {grid-gap: 0; padding-left:55px}
    .kes-blur {filter: blur(4px); transition-duration: 0.5s}
		.kes-blur-large {filter: blur(15px)}
    .kes-blur:hover {filter: none}
  `;
  document.head.appendChild(style);
  
  
  const allSettings = [
    {name: "Show domains", value:"show-domains"},
    {name: "Show collapse comment", value:"show-collapse"},
    {name: "Show collapse replies", value:"show-collapse-replies"},
    {name: "Replies start collapsed", value:"start-collapse-replies", default: "false"},
    {name: "Move comment box to top", value:"comment-box-top"},
    {name: "Reply box improvements", value:"comment-cancel"},
    {name: "Hide known NSFW domains", value:"nsfw-hide"},
    {name: "Blur known NSFW domains", value:"nsfw-blur"},
    {name: "Hide random sidebar posts", value:"hide-random"},
    {name: "Add OP tag", value:"op-tag"}
  ];
  
  allSettings.forEach(setting => {
    if (setting.default === "false" && localStorage.getItem("setting-" + setting.value) === null) {
      localStorage.setItem("setting-" + setting.value, "false");
    }
  });
  
  function getSetting(setting) {
    let value = localStorage.getItem("setting-" + setting);
    if (value === null)
      value = "true";
    return value === "true";
  }
  function setSetting(setting, value) {
    localStorage.setItem("setting-" + setting, value);
    location.reload();
  }
  
  
  function addDomain(link) {
    const parts = link.title.split("@");
    if (parts[2] !== location.hostname && !link.innerText.includes("@" + parts[2])) {
      const linkText = link.childNodes[link.childNodes.length-1];
      linkText.nodeValue += "@" + parts[2];
    }
  }
  function addDomains() {
    document.querySelectorAll(".magazine-inline, .user-inline").forEach(link => {
      addDomain(link);
    });
    
    
    const config = { childList: true };
    const callback = (mutationList, observer) => {
      for (const mutation of mutationList) {
        mutation.addedNodes.forEach(container => {
        	container.querySelectorAll(".magazine-inline, .user-inline").forEach(link => {
            addDomain(link);
          });
        });
      }
    };

    const observer = new MutationObserver(callback);
    const content = document.querySelector("div#content > div");
    if (content)
    	observer.observe(content, config);
  }
  
  
  function getComments(comment, allComments) {
    const id = comment.id.split('-')[2];
    
    allComments.push(comment);
    const subComments = comment.parentElement.querySelectorAll('blockquote[data-subject-parent-value="'+id+'"]');
    subComments.forEach(blockquote => { getComments(blockquote, allComments); });
  }
  function getCollapsos(comment, allCollapsos) {
    const id = comment.id.split('-')[2];
    
    if (comment.classList.contains('kes-expand'))
    	allCollapsos.push(comment);
    
    const subComments = comment.parentElement.querySelectorAll('blockquote[data-subject-parent-value="'+id+'"]');
    subComments.forEach(blockquote => { getCollapsos(blockquote, allCollapsos); });
  }
  function removeAllCollapsos(blockquote) {
    // Just remove all these for now, don't want to figure out how to do this cleanly right now.
    const allCollapsos = [];
    getCollapsos(blockquote, allCollapsos);
    allCollapsos.forEach(comment => { comment.remove() });
  }
  function expandComment(blockquote) {
    const allComments = [];
    getComments(blockquote, allComments);
    allComments.forEach(comment => { comment.style.display="" });
    
    removeAllCollapsos(blockquote);
  }
  function collapseComment(blockquote) {
    const id = blockquote.id.split('-')[2];
    let commentLevel = "1";
    blockquote.classList.forEach(classItem => {
    	if (classItem.includes("comment-level"))
        commentLevel = classItem.split("--")[1];
    });
    
    const allComments = [];
    getComments(blockquote, allComments);
    allComments.forEach(comment => { comment.style.display="none" });

    const username = blockquote.querySelector("header a").innerText;
    const time = blockquote.querySelector("header time").innerText;

    const newBlockquote = document.createElement('blockquote');
    newBlockquote.className = 'kes-expand section comment entry-comment comment-level--' + commentLevel;
    newBlockquote.dataset.subjectParentValue = id;
    newBlockquote.innerHTML = '<header><a href="javascript:;">' + username + ', ' + time + ' [+]</a></header>';
    newBlockquote.querySelector('a').addEventListener("click", () => {expandComment(blockquote)});
    blockquote.parentNode.insertBefore(newBlockquote, blockquote);
  }
  function getTotalReplyCount(blockquote) {
    const allComments = [];
    getComments(blockquote, allComments);
    return allComments.length - 1;
  }
  
  function addCollapseLinks() {
    if (location.pathname.startsWith('/m')) {
      const comments = document.querySelectorAll("blockquote.comment");
      comments.forEach(blockquote => {
      	const menu = blockquote.querySelector("header");
        if (!menu.innerText.includes('[-]')) {
          const newA = document.createElement('a');
          newA.href = "javascript:;";
          newA.className = "kes-collapse";
          newA.innerHTML = '[-]';
          menu.appendChild(newA);
        }
      });
      
      document.querySelectorAll(".kes-collapse").forEach(link => {link.addEventListener("click", () => {
        const blockquote = link.closest("blockquote.comment");
        collapseComment(blockquote);
      })});
    }
  }
  
  function getAllReplies(blockquote) {
    const allComments = [];
    getComments(blockquote, allComments);
    allComments.splice(allComments.indexOf(blockquote), 1);
    return allComments;
  }
  
  
  function toggleReplies(blockquote, display) {
    const id = blockquote.id.split('-')[2];
    const allReplies = getAllReplies(blockquote);
    
    let anyHidden = false;
    allReplies.forEach(reply => { 
      if (reply.style.display == 'none')
        anyHidden = true;
    });
    
    allReplies.forEach(comment => { comment.style.display = anyHidden ? '' : 'none' });
    
    removeAllCollapsos(blockquote);
  }
  
  function addCollapseRepliesLinks() {
    if (location.pathname.startsWith('/m')) {
      const comments = document.querySelectorAll("blockquote.comment-level--1");
      comments.forEach(blockquote => {
        const id = blockquote.id.split('-')[2];
        const subComments = blockquote.parentElement.querySelectorAll('blockquote[data-subject-parent-value="'+id+'"]');
        
        if (subComments.length > 0) {
          const menu = blockquote.querySelector("footer menu");
          const newLi = document.createElement('li');
          newLi.innerHTML = '<a href="javascript:;" class="kes-collapse-replies">toggle replies ('+getTotalReplyCount(blockquote)+')</a>';
          menu.appendChild(newLi);
        }
      });
      
      document.querySelectorAll(".kes-collapse-replies").forEach(link => {link.addEventListener("click", () => {
        const blockquote = link.closest("blockquote.comment");
        
        toggleReplies(blockquote);
      })});
    }
  }
  function collapseAllReplies() {
    const comments = document.querySelectorAll("blockquote.comment-level--2");
    comments.forEach(blockquote => {
      collapseComment(blockquote);
    });
  }
  
  
  function moveCommentBox() {
    const commentAdd = document.querySelector('#comment-add');
    if (commentAdd)
    	commentAdd.parentNode.insertBefore(commentAdd, document.querySelector('#comments'));
  }
  
  
  function removeReplyBox(container) {
    container.innerHTML = '';
    container.style = '';
  }
  function addCommentCancelButton(container) {
    const list = container.querySelector('div.actions ul');
    
    const newLi = document.createElement('li');
    newLi.innerHTML = '<div><button class="btn btn__primary">Cancel</button></div>';
    list.appendChild(newLi);
    newLi.querySelector('button').addEventListener("click", () => { removeReplyBox(container) });
  }
  function fixMarkdownButtons(form) {
    const formActionSplit = form.action.split('/');
    const newId = 'entry_comment_body_' + formActionSplit[formActionSplit.length-1];
    
    form.querySelector('#entry_comment_body').id = newId;
    form.querySelector('markdown-toolbar').setAttribute('for', newId);
  }
  function observeReplyAdded() {
    const config = { childList: true };
    const callback = (mutationList, observer) => {
      for (const mutation of mutationList) {
        const container = mutation.target;
        const form = container.querySelector('form.comment-add');
        
        if (form !== null) {
          fixMarkdownButtons(form);
          addCommentCancelButton(container);
        }
      }
    };

    const observer = new MutationObserver(callback);
    document.querySelectorAll('blockquote.comment footer div.js-container').forEach(container => { observer.observe(container, config) })
  }
  
  
  // Thanks u/le__el
  function addBlur(container) {
    container.querySelectorAll("img").forEach(el => {
        el.classList.add('kes-blur');
    });
    container.querySelectorAll("figure img").forEach(el => {
        el.classList.add('kes-blur-large');
    });
  }
  const nsfwDomains = [
    "lemmynsfw.com",
    "redgifs.com"
  ];
  function hideNSFW() {
    document.querySelectorAll("article").forEach(article => {
      const magazineInline = article.querySelector(".magazine-inline");
      const entryDomain = article.querySelector(".entry__domain a");
      if (article.querySelector("small.danger") !== null
         || (entryDomain && nsfwDomains.includes(entryDomain.innerText))
         || ( magazineInline && nsfwDomains.includes(magazineInline.title.split('@')[2]) )
         ) {
        if (getSetting("nsfw-hide")) {
      		article.remove();
        } else {
          addBlur(article);
        }
      }
    });
  }
  
  function hideRandom() {
    const posts = document.querySelector('#sidebar section.posts');
    if (posts)
      posts.style = "display:none;";
    
    const entries = document.querySelector('#sidebar section.entries');
    if (entries)
      entries.style = "display:none;";
  }
  
  function addOPTag() {
    document.querySelectorAll('blockquote.author > header').forEach(header => {
    	const opTag = document.createElement('small');
      opTag.className = "badge kbin-bg";
      opTag.innerText = "OP";
      header.appendChild(opTag);
    });
  }
  
  
  function generateSettingDiv(settingDisplay, setting) {
    const settingValue = getSetting(setting);
    const newDiv = document.createElement('div');
    newDiv.className = "row";
    newDiv.innerHTML = `<span>${settingDisplay}:</span>
      <div>
        <a class="kes-setting-yes link-muted ${settingValue ? 'active' : ''}" href="javascript:;" data-setting="${setting}">
          Yes
        </a>
        |
        <a class="kes-setting-no link-muted ${settingValue ? '' : 'active'}" href="javascript:;" data-setting="${setting}">
          No
        </a>
      </div>`;
    
    return newDiv;
  }
  function addHTMLSettings() {
    const settingsList = document.querySelector(".settings-list");
    
    const header = document.createElement('strong');
    header.textContent = "kbin enhancement script";
    settingsList.appendChild(header);
    
    allSettings.forEach(setting => { settingsList.appendChild(generateSettingDiv(setting.name, setting.value)) });
                             
    document.querySelectorAll(".kes-setting-yes").forEach(link => { link.addEventListener("click", () => {setSetting(link.dataset.setting, true) })});
    document.querySelectorAll(".kes-setting-no").forEach(link => { link.addEventListener("click", () => {setSetting(link.dataset.setting, false) })});
  }
  
  addHTMLSettings();
  if (getSetting("show-domains"))
    addDomains();
  if (getSetting("show-collapse"))
    addCollapseLinks();
  if (getSetting("show-collapse-replies"))
    addCollapseRepliesLinks();
  if (getSetting("start-collapse-replies"))
    collapseAllReplies();
  if (getSetting("comment-box-top"))
    moveCommentBox();
  if (getSetting("comment-cancel"))
    observeReplyAdded();
  if (getSetting("nsfw-blur") || getSetting("nsfw-hide"))
    hideNSFW();
  if (getSetting("hide-random"))
    hideRandom();
  if (getSetting("op-tag"))
    addOPTag();
  
  
  
  
  if (localStorage.getItem("setting-changelog-version") != version) {
    const message = `<strong>kbin enhancement script version: ${version}</strong><br>
			Thanks for downloading! You can always toggle on and off features in the kbin sidebar settings.<br>Recent changes:
			<ul>
        <li>OP tag in comments</li>
        <li>Hide random sidebar</li>
				<li>Fixed infinite scroll not showing domains</li>
        <li>Additional NSFW protection</li>
				<li>Fixed markdown buttons and added "Cancel" when replying</li>
        <li>Bug Fixes</li>
			</ul>
		`
    
    
    const versionDiv = document.createElement('div');
    versionDiv.id = 'kes-version-dialog';
    versionDiv.innerHTML = '<div class="kes-version-dialog-content">'+message+'<br><button>Close</button></div>';
    document.body.appendChild(versionDiv);
    
    document.querySelector('#kes-version-dialog button').addEventListener("click", () => {
      document.querySelector('#kes-version-dialog').remove();
      localStorage.setItem("setting-changelog-version", version);
    });
  }
})();