kbin enhancement script

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

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

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