kbin enhancement script

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

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==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);
    });
  }
})();