[kddit] Search Button & Settings Button & UI Modifications

Adds search and settings buttons and modifies some UIs.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         [kddit] Search Button & Settings Button & UI Modifications
// @match        https://kddit.kalli.st/*
// @noframes
// @run-at       document-body
// @inject-into  content
// @grant        GM.deleteValue
// @grant        GM.deleteValues
// @grant        GM.getValue
// @grant        GM.getValues
// @grant        GM.setValues
// @namespace    Violentmonkey Scripts
// @author       SedapnyaTidur
// @version      1.0.3
// @license      MIT
// @revision     5/8/2026, 5:22:22 PM
// @description  Adds search and settings buttons and modifies some UIs.
// ==/UserScript==

(async function() {
  'use strict';

  // https://gt.kalli.st/czar/kddit.kalli.st
  // https://github.com/kzarist/kddit

  const defaultSettings = {
    autoplay_videos: false,
    bottom_gap: '0px',
    comment_scroll: true,
    external_newtab: true,
    font_scale: 1,
    nsfw_contents: true,
    preload_images: false,
    preload_videos: false,
    remember_search: false,
    search_input: undefined,
    video_quality: 1080,
    comment_sort: 'confidence',
    post_sort: 'hot',
    user_sort: 'new',
    search_sort: 'relevance',
    author_search_sort: 'relevance',
    flair_search_sort: 'relevance',
    self_search_sort: 'relevance',
    selftext_search_sort: 'relevance',
    site_search_sort: 'relevance',
    subreddit_search_sort: 'relevance',
    title_search_sort: 'relevance',
    url_search_sort: 'relevance'
  };

  const settings = await GM.getValues(defaultSettings);

  const style = document.createElement('style');
  style.textContent = `
  :root {
    --scale: ${settings.font_scale};
    --bottom-gap: ${settings.bottom_gap};
    --font: normal;
    --author: #00AACC;
    --border: #555555;
    --deleted: #C55585;
    --link: #4682B4;
    --replies: #008B8B;
    --text: #C0C0C0;
    --user: #BDBB66;
    --visited: #AA88DD;
    --votes: #EEBBBB;
    --blue: #6080A0;
    --green: #5CCC85;
    --orange: #906000;
    --red: #AA4455;
  }
  :root, head, body { background: #000000; }
  h1, h2, h3 { font-size: calc(22px * var(--scale)); }
  a:active, a:hover { text-decoration: underline; }
  .header { display: flex; flex-direction: row; align-items: center; margin: 5px 0px; padding: 10px; }
  .header .main-link, .header .subreddit-link { white-space: nowrap; margin-right: 10px; }
  .header .subreddit-link, .post-link, .comments > p:last-child { display: none; }
  .header, .menu { border: none; }
  .header a, .menu a, .button, .md p, p, b, pre code, li, .inner-post a, .votes, .post-info, .post-info .sub-link, .post-info a, .comment-info, .comment-info a, .flair, .nav a { font-family: var(--font); font-size: calc(16px * var(--scale)); color: var(--text); }
  .container { max-width: none; margin: 0px; }
  .content:has(:first-child[class="menu"]), .comments { display: flex; flex-direction: column; }
  .content { padding: 0px 0px var(--bottom-gap) 0px; }
  .menu { margin: 5px 0px; }
  .menu a::first-letter { text-transform: capitalize; }
  .menu a:active, .menu a:hover, .menu .focus, .button:active, .button:hover { background-color: var(--red); color: #000000; }
  .post { display: flex; flex-direction: column; border: 1px solid var(--border); padding: 0px 10px; margin: 5px 0px; width: auto; }
  .post:active, .post:hover { border: 1px solid var(--border); }
  .votes { position: absolute; color: var(--votes); width: auto; margin: 10px; }
  .inner-post, .comment { display: flex; flex-direction: column; align-items: start; margin: 0px; }
  .inner-post { position: static; margin-left: calc(60px * var(--scale)); }
  .inner-post a[href^="/r/"] { padding: 20px 0px; }
  .inner-post a b, .comment a b { font-weight: 500; }
  .inner-post a:visited b { color: var(--visited); }
  .inner-post .post { width: 98%; }
  .comment-info a[href^="/u/"] { color: var(--user); font-weight: 500; }
  .post-info .sub-link, .comment-info .sub-link, .post-content a[href^="/r/"], .comment-content a[href^="/r/"] { color: var(--red); }
  /* .author */
  .post-info a[href^="/u/"], .comment .comment-info .author, .comment-content a[href^="/u/"] { color: var(--author); }
  .post-info a[href^="/u/[deleted]"], .comment-info a[href^="/u/[deleted]"] { color: var(--deleted); }
  /* .moderator */
  .post-info a[href^="/u/AutoModerator"], .inner-post .post-info .moderator, .comment-info a[href^="/u/AutoModerator"], .comment .comment-info .moderator { color: var(--green); }
  /* .link */
  .post-info a[href^="/domain/"], .post-info a[target="_blank"], .post-content a:not([href^="/u/"], [href^="/r/"]), .comment-info .link, .comment-content a:not([href^="/u/"], [href^="/r/"]) { color: var(--link); font-weight: 400; }
  .flair { height: auto; width: auto; padding: 5px 10px; margin: 5px 0px; }
  .inner-post .flair { background: none; color: var(--green); border: 1px solid var(--green); }
  .post-content, .post-content > *, .comment-content, ul:has(> .reply), .reply { display: flex; flex-direction: column; align-items: start; width: auto; margin: auto; }
  .nav { margin-top: 0px; margin-bottom: 5px; }
  .nav .button { border: 1px solid var(--border); margin: 10px; padding: 10px; }
  .comment { border: 1px solid var(--border); margin: 5px 0px; padding: 10px; }
  .comment hr { border: 1px solid var(--text); margin: 10px 0px; }
  .comment > a { padding: 0px 0px 20px 0px; }
  .comment-info { margin: 0px 0px 10px 0px; }
  .comment-info .flair { background: var(--border); border: 1px solid var(--border); border-bottom-right-radius: calc(16px * var(--scale)); border-top-left-radius: calc(16px * var(--scale)); }
  .comment-info span:not([class]) { color: var(--votes); margin: 0px 5px; }
  .comment-info .link { font-size: calc(20px * var(--scale)); line-height 0px; margin: 0px 10px 0px 15px; }
  .post-content:has(> .md), .comment ul, .comment-content, .md pre { margin: 0px; }
  .reply, blockquote { padding: 0px 0px 0px 10px; }
  .reply { border-left-style: solid; border-left-width: 2px; margin: 10px 0px 0px 0px; }
  ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply,
  ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply,
  ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply,
  ul > .reply > ul > .reply > ul > .reply > ul > .reply,
  ul > .reply > ul > .reply { border-left-color: var(--orange); }
  ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply,
  ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply,
  ul > .reply > ul > .reply > ul > .reply > ul > .reply > ul > .reply,
  ul > .reply > ul > .reply > ul > .reply, ul > .reply { border-left-color: var(--blue); }
  blockquote { border-left: 4px solid var(--red); margin: 10px 0px; }
  pre code { max-width: 96vw; font-family: monospace; background: #303030; padding: 10px; margin: 0px; overflow: auto; scrollbar-width: none; }
  .media { margin: auto; }
  .media img, .media video { height: auto; min-height: 400px; max-height: 80vh; }
  /* Long Post & Comment */
  .long-post { max-height: 80vh; overflow: hidden; }
  #expand-collapse { display: flex; flex-direction: column; font-weight: bold; padding: 20px 0px; align-items: center; width: auto; margin: auto; }
  /* Sliding Images */
  .css-slider-mask, .slide-gfx .media img { height: auto; min-height: 400px; max-height: 80vh; }
  .slide { background: none; }
  body .container .css-slider { display: flex; flex-direction: row; flex-grow: 1; align-items: center; justify-content: center; width: auto; height: auto; user-select: none; }
  .css-slider li { display:flex; height: auto; margin: auto; }
  .css-slider li:not(:last-child), .css-slider li[active="false"], li[active="true"] + #previous-slide, li:first-child[active="true"] ~ #next-slide { display: none; }
  .css-slider li:last-child, .css-slider li[active="true"] { display: block; }
  #previous-slide, #next-slide { display: flex; flex-direction: column; justify-content: center; position: absolute; height: 100%; color: var(--text); font-size: calc(32px * var(--scale)); text-shadow: -2px -2px 0 var(--border), 2px -2px 0 var(--border), -2px 2px 0 var(--border), 2px 2px 0 var(--border); }
  #previous-slide:active, #previous-slide:hover, #next-slide:active, #next-slide:hover { color: var(--green); }
  #previous-slide { left: 10px; padding: 0px 30px 0px 10px; }
  #next-slide { right: 10px; padding: 0px 10px 0px 30px; }
  /* Search Bar & Settings */
  .custom-font { color: var(--text); font-family: var(--font); font-size: calc(16px * var(--scale)); }
  .custom-highlight:active, .custom-highlight:hover { color: var(--red); }
  .unselectable { user-select: none; }
  #search-bar, #search-text, #search-label, #help-row { display: flex; flex-direction: row; align-items: center; width: auto; height: auto; margin: auto; }
  #search-bar { max-width: 80%; padding: 0px 10px; border: 2px solid var(--border); }
  #search-bar:has(> #search-text:focus) { border: 2px solid var(--red); }
  #search-text { flex-grow: 1; outline: none; overflow: auto; padding: 10px 10px 10px 0px; scrollbar-width: none; }
  #search-text, #search-help, #search-label, #search-button { background: none; border: none; }
  #search-help { font-size: calc(20px * var(--scale)); font-weight: bold; margin-right: calc(5px * var(--scale)); }
  #search-checkbox { accent-color: var(--red); margin: 0px calc(10px * var(--scale)); transform: scale(calc(1.4 * var(--scale)), calc(1.4 * var(--scale))); }
  #search-label { font-weight: 500; padding: 10px; overflow: auto; white-space: nowrap; scrollbar-width: none; }
  #search-button { font-size: calc(45px * var(--scale)); line-height: calc(40px * var(--scale)); font-weight: 500; }
  #settings-button { font-size: calc(28px * var(--scale)); font-weight: bold; line-height: calc(40px * var(--scale)); margin-left: 10px; }
  #settings-container, #help-container { position: absolute; top: calc(65px * var(--scale)); padding-bottom: var(--bottom-gap); z-index: 2; }
  #settings-container { right: 10px; }
  #settings-content, #help-content { background: #000000; border: 2px solid var(--border); }
  #settings-header, #settings-save, #settings-reset, #help-header { flex-grow: 1; justify-content: center; font-weight: 700; }
  #settings-body { background: var(--red); }
  #settings-selection { overflow: wrap; justify-content: space-between; }
  .settings-select { text-align-last: right; }
  #settings-label + select { background: none; border: none; }
  #settings-save:active, #settings-save:hover, #settings-reset:active, #settings-reset:hover { background: var(--red); color: #000000; }
  #help-container { padding: 0px 10px var(--bottom-gap) 10px; align-self: center; }
  #help-body, #help-body > * { display: flex; flex-direction: column; align-items: start; }
  #help-body { background: #000000; }
  #help-body > * { margin: 5px 0px; }
  #help-row { flex-grow: 1; margin: 0px; padding: 0px 10px; white-space: pre-wrap; }
  #help-title { font-style: italic; }
  #help-title { color: var(--blue); min-width: calc(90px * var(--scale)); }
  #help-description, #help-example { margin: 0px 0px 0px 10px; }
  #help-example { color: var(--green); user-select: text; }
  /* Comment Collapser */
  .comment [hidden] { display: none; }
  #collapser { display: inline-block; font-size: calc(18px * var(--scale)); margin: 0px 10px 0px 0px; transform: rotate(90deg); }
  #collapser[collapsed="true"] { transform: rotate(0deg); }
  /* See More Replies */
  .see-more-replies { color: var(--replies); font-style: italic; font-weight: 500; margin: 20px 0px 10px 0px; }
  .see-more-replies:active, .see-more-replies:hover { text-decoration: underline; }`;
  document.head.appendChild(style);

  // Helper function.
  // Returns a substring or 0.
  const substr = function(regex, str) {
    const arr = regex.exec(str);
    return arr && arr.length === 2 ? arr[1] : 0;
  };

  // TODOs:
  // const disableAutoplayGifs = async function() {};
  // const muteSubreddits = async function() {}; // hides disliked subreddits.
  // const highlightSearchText = async function() {};


  const hideLongPostsAndComments = async function() {
    const onclick = async function(event) {
      if (!event.target) return;
      const target = event.target.parentElement.querySelector(':scope > .long-post');
      if (target) {
        target.classList.remove('long-post');
        event.target.innerText = '⬆ COLLAPSE ⬆';
      } else {
        event.target.parentElement.querySelector(':scope > .md').classList.add('long-post');
        event.target.innerText = '⬇ EXPAND ⬇';
      }
    };

    const height = window.screen.height << 1;
    document.body.querySelectorAll(':is(.post-content, .comment-content) > .md ').forEach(async element => {
      if (element.getBoundingClientRect().height < height) return;
      element.classList.add('long-post');
      const button = document.createElement('div');
      button.id = 'expand-collapse';
      button.className = 'custom-font unselectable';
      button.textContent = '⬇ EXPAND ⬇';
      button.onclick = onclick;
      element.parentElement.appendChild(button);
    });
  };

  // https://support.reddithelp.com/hc/en-us/articles/19696541895316-Available-search-features
  const addSearchBar = async function() {
    let inputText, checkbox, sort, thread;

    // author, flair, self, selftext, site, subreddit, title, url
    if ((thread = substr(/^\/(r\/[^/]+)/, window.location.pathname))) {
      sort = settings.subreddit_search_sort;
    } else if ((thread = substr(/^\/(u\/[^/]+)/, window.location.pathname))) {
      sort = settings.author_search_sort;
    } else if ((thread = substr(/(?:=|\+|%20)((?:author|flair|self|selftext|site|subreddit|title|url)(?:%3A|:)[^+&]+)/, window.location.search))) {
      thread = decodeURIComponent(thread);
      sort = settings[thread.replace(/^([^:]+).+$/, '$1') + '_search_sort'];
    } else {
      thread = 'r/popular';
      sort = settings.search_sort;
    }

    const onsearch = async function(event) {
      if (event.type === 'keydown' && event.key !== 'Enter') return;
      document.activeElement?.blur(); // Hide virtual keyboard.
      let input = inputText.value.trim().replace(/\s/g, ' ');
      if (!input) { inputText.value = ''; return; } // Delete previous whitespaces input.
      let arr = [], ignoreCheckbox = false, index = 0, path = '/', rest = '', search = '';

      input.replace(/(?:"[^"]*"|'[^']*')/g, match => {
        if (match === '""' || match === "''") return '';
        arr.push(match.replace(/^["']([ru]\/[a-zA-Z0-9_-]+)["']$/, '$1'));
        return '\n';
      }).match(/(?:\n|[^\n ]+)/g).forEach(field => {
        if (!field) return;
        if (field === '\n') {
          search += (search ? ' ' : '') + arr[index++];
        } else if (path === '/' && /^[ru]\/[a-zA-Z0-9_-]+$/.test(field)) {
          path += field + '/'
        } else if (/^u\/[a-zA-Z0-9_-]+$/.test(field)) {
          search += (search ? ' ' : '') + 'author:' + field.replace(/^u\/(.+)$/, '$1');
        } else {
          search += (search ? ' ' : '') + field;
        }
      });

      arr = undefined;
      if (!search && path === '/') return;

      GM.setValues({search_checked: checkbox.checked, search_input: (settings.remember_search ? input : undefined) });
      if (!search) {
        const url = window.location.origin + path.replace(/\/$/, '') +
              (path.startsWith('/r') && settings.post_sort !== 'hot' ? '/' + settings.post_sort :
              (path.startsWith('/u') && settings.user_sort !== 'new' ? '/' + settings.user_sort : ''));
        window.location.href === url ? window.location.reload() : window.location.href = url;
        return;
      }
      if (path.startsWith('/u')) {
        search = path.replace(/^\/u\/([^/]+)\/$/, 'author:$1') + (search ? ' ' + search : '');
        path = '/';
        sort = settings.author_search_sort;
        ignoreCheckbox = true;
      } else if (path === '/' && !ignoreCheckbox && checkbox.checked) {
        if (thread.startsWith('r/')) {
          path += thread + '/';
        } else if (thread.startsWith('u/')) {
          search = thread.replace(/^u\/(.+)$/, 'author:$1') + (search ? ' ' + search : '');
        } else {
          search = thread + (search ? ' ' + search : '');
        }
      }
      if (path !== '/') {
        rest += '&restrict_sr=True';
        sort = settings.subreddit_search_sort;
        ignoreCheckbox = true;
      }
      rest += '&include_over_18=' + (settings.nsfw_contents ? true : false);
      rest += ((ignoreCheckbox || checkbox.checked) ? (sort !== 'relevance' ? '&sort=' + sort : '') :
              (settings.search_sort !== 'relevance' ? '&sort=' + settings.search_sort : ''));

      const url = window.location.origin + path + 'search?' + new URLSearchParams({ q: search }) + rest;
      window.location.href === url ? window.location.reload() : window.location.href = url;
    };

    const createHelp = function(title, description, examples) {
      const container = document.createElement('div');
      const row = document.createElement('div');
      row.id = 'help-row';
      const title_span = document.createElement('span');
      title_span.id = 'help-title';
      title_span.className = 'custom-font';
      title_span.textContent = title;
      const description_span = document.createElement('span');
      description_span.id = 'help-description'
      description_span.className = 'custom-font';
      description_span.textContent = description;

      row.appendChild(title_span);
      row.appendChild(description_span);
      container.appendChild(row);

      examples.forEach(element => {
        const row = document.createElement('div');
        row.id = 'help-row';
        const forexample_span = document.createElement('span');
        forexample_span.id = 'help-forexample';
        forexample_span.className = 'custom-font';
        forexample_span.textContent = 'For example,';
        const example_span = document.createElement('span');
        example_span.id = 'help-example';
        example_span.className = 'custom-font';
        example_span.textContent = element;

        row.appendChild(forexample_span);
        row.appendChild(example_span);
        container.appendChild(row);
      });
      return container;
    };

    const container = document.createElement('div');
    container.id = 'search-bar';
    inputText = document.createElement('input');
    inputText.id = 'search-text';
    inputText.className = 'custom-font';
    inputText.type = 'text';
    inputText.placeholder = 'Search';
    inputText.title = 'Search something in kddit';
    inputText.maxLength = 60;
    inputText.value = settings.search_input ? settings.search_input.substr(0, 60) : '';
    inputText.setSelectionRange(-1, -1);
    inputText.onkeydown = onsearch;

    let help_container;
    const help = document.createElement('div');
    help.id = 'search-help';
    help.className = 'custom-font custom-highlight unselectable';
    help.textContent = 'ⓘ';
    help.onclick = async function(event) {
      if (help_container && help_container.opened) {
        help_container.opened = false;
        help_container.remove();
      } else if (help_container) {
        help_container.opened = true;
        document.body.appendChild(help_container);
      } else {
        help_container = document.createElement('div');
        help_container.id = 'help-container';
        help_container.className = 'unselectable';
        help_container.opened = true;
        const content = document.createElement('div');
        content.id = 'help-content';
        const header = document.createElement('div');
        header.id = 'help-header';
        header.className = 'header custom-font';
        header.textContent = 'Search Features';
        const body = document.createElement('div');
        body.id = 'help-body';
        body.appendChild(createHelp('r/subreddit', 'Go to, search in this subreddit, or search as text', ['r/cats', 'r/cats "hello kitty"', '"r/cats"', '"r/cats" "hello kitty"']));
        body.appendChild(createHelp('u/user', 'Go to, search in this user who submitted the post, or search as text.', ['u/reddit', 'accounts u/reddit', "'u/reddit'"]));
        body.appendChild(createHelp('author', 'The user who submitted the post.', ['author:reddit']));
        body.appendChild(createHelp('flair', 'The text of the link flair on the post.', ['flair:cats']));
        body.appendChild(createHelp('self', 'Filter by text post. Set to true to filter to only text posts, false otherwise.', ['self:true']));
        body.appendChild(createHelp('selftext', 'The body of the post.', ['selftext:cat']));
        body.appendChild(createHelp('site', 'The domain of the submitted URL.', ['site:example.com']));
        body.appendChild(createHelp('subreddit', "The submission's subreddit.", ['subreddit:cats', 'subreddit:cats kitten']));
        body.appendChild(createHelp('title', 'The submission title.', ['title:cat']));
        body.appendChild(createHelp('url', "The submission's URL (the website's address).", ['url:cats']));
        body.appendChild(createHelp('AND', 'Must contain these words in the results.', ['kitten AND cat', 'kitten AND cat AND dog', '"r/cats" AND kitten']));
        body.appendChild(createHelp('OR', 'Must contain one of these words in the results.', ['kitten OR cat', '(kitten OR cat) NOT dog']));
        body.appendChild(createHelp('NOT', 'Must not contain this word or these words in the results.', ['NOT dog', 'NOT cat NOT dog', 'r/cats kitten NOT cat', 'kitten NOT (cat OR dog)']));

        content.appendChild(header);
        content.appendChild(body);
        help_container.appendChild(content);
        document.body.appendChild(help_container);
      }
    };
    checkbox = document.createElement('input');
    checkbox.id = 'search-checkbox';
    checkbox.type = 'checkbox';
    checkbox.checked = await GM.getValue('search_checked', true);
    const label = document.createElement('label');
    label.id = 'search-label';
    label.className = 'custom-font';
    label.for = 'search-checkbox';
    label.textContent = 'in ' + thread;
    const button = document.createElement('div');
    button.id = 'search-button';
    button.className = 'custom-font custom-highlight unselectable';
    button.textContent = '⌕';
    button.onclick = onsearch;

    container.appendChild(inputText);
    container.appendChild(help);
    container.appendChild(checkbox);
    container.appendChild(label);
    container.appendChild(button);
    document.body.querySelector(':scope > .header')?.appendChild(container);
  };

  const addSettings = async function() {
    const tempSettings = {};

    const createMenu = function(title, tag, selectedValue, choices) {
      if (/^(?:autoplay_videos|comment_scroll|external_newtab|nsfw_contents|preload_images|preload_videos|remember_search)$/.test(tag)) {
        selectedValue = (selectedValue ? 'Yes' : 'No');
      } else if (tag === 'font_scale') {
        selectedValue = (selectedValue + '.0').replace(/^([0-9]+\.[0-9]+).*$/, '$1x');
      } else if (tag === 'video_quality') {
        selectedValue += 'p';
      } else if (tag.endsWith('sort'))  {
        selectedValue = selectedValue[0].toUpperCase() + selectedValue.substr(1);
      }

      const container = document.createElement('div');
      container.id = 'settings-selection';
      container.className = 'menu';
      const label = document.createElement('label');
      label.id = 'settings-label';
      label.className = 'custom-font';
      label.for = 'settings-select-' + tag;
      label.textContent = title;
      const selection = document.createElement('select');
      selection.id = 'settings-select-' + tag;
      selection.className = 'custom-font settings-select';
      selection.onchange = async function(event) {
        const id = event.target.id.replace('settings-select-', '');
        switch (id) {
          case 'bottom_gap':
            tempSettings.bottom_gap = event.target.value;
          break;
          case 'autoplay_videos':
          case 'comment_scroll':
          case 'external_newtab':
          case 'nsfw_contents':
          case 'preload_images':
          case 'preload_videos':
          case 'remember_search':
            tempSettings[id] = (event.target.value === 'Yes' ? true : false);
          break;
          case 'font_scale':
            tempSettings.font_scale = Number(event.target.value.replace('x', ''));
          break;
          case 'video_quality':
            tempSettings.video_quality = Number(event.target.value.replace('p', ''));
          break;
          case 'comment_sort':
          case 'post_sort':
          case 'user_sort':
          case 'search_sort':
          case 'author_search_sort':
          case 'flair_search_sort':
          case 'self_search_sort':
          case 'selftext_search_sort':
          case 'site_search_sort':
          case 'subreddit_search_sort':
          case 'title_search_sort':
          case 'url_search_sort':
            tempSettings[id] = event.target.value.toLowerCase();
          break;
        }
      };
      choices.forEach(choice => {
        const option = document.createElement('option');
        if (choice === selectedValue) option.selected = true;
        option.value = choice;
        option.textContent = choice;
        selection.appendChild(option);
      });
      container.appendChild(label);
      container.appendChild(selection);
      return container;
    };

    let container;
    const button = document.createElement('div');
    button.id = 'settings-button';
    button.className = 'custom-font custom-highlight unselectable';
    button.textContent = '⫶☰';
    button.onclick = async function(event) {
      if (container && container.opened) {
        container.opened = false;
        container.remove();
        return;
      } else if (container) {
        container.opened = true;
        document.body.appendChild(container);
      } else {
        container = document.createElement('div');
        container.id = 'settings-container';
        container.className = 'unselectable';
        container.opened = true;
        const content = document.createElement('div');
        content.id = 'settings-content';
        const header = document.createElement('div');
        header.id = 'settings-header';
        header.className = 'header custom-font';
        header.textContent = 'Settings';
        const body = document.createElement('div');
        body.id = 'settings-body';
        body.appendChild(createMenu('Autoplays Videos:', 'autoplay_videos', settings.autoplay_videos, ['Yes', 'No']));
        body.appendChild(createMenu('Autoscrolls to Comments:', 'comment_scroll', settings.comment_scroll, ['Yes', 'No']));
        body.appendChild(createMenu('Bottom Gap:', 'bottom_gap', settings.bottom_gap, ['0px', '20px', '40px', '60px', '80px', '100px']));
        body.appendChild(createMenu('External Links In New Tab:', 'external_newtab', settings.external_newtab, ['Yes', 'No']));
        body.appendChild(createMenu('Font Scaling:', 'font_scale', settings.font_scale, ['0.8x', '0.9x', '1.0x', '1.1x', '1.2x', '1.3x', '1.4x', '1.5x']));
        body.appendChild(createMenu('NSFW Contents:', 'nsfw_contents', settings.nsfw_contents, ['Yes', 'No']));
        body.appendChild(createMenu('Preloads Images:', 'preload_images', settings.preload_images, ['Yes', 'No']));
        body.appendChild(createMenu('Preloads Videos:', 'preload_videos', settings.preload_videos, ['Yes', 'No']));
        body.appendChild(createMenu('Remembers Search Input:', 'remember_search', settings.remember_search, ['Yes', 'No']));
        body.appendChild(createMenu('Video Quality:', 'video_quality', settings.video_quality, ['220p', '270p', '360p', '480p', '720p', '1080p']));
        body.appendChild(createMenu('Comments Sorting:', 'comment_sort', settings.comment_sort, ['Confidence', 'Top', 'New', 'Controversial', 'Old']));
        body.appendChild(createMenu('Posts Sorting:', 'post_sort', settings.post_sort, ['Hot', 'New', 'Top', 'Rising', 'Controversial']));
        body.appendChild(createMenu('User Sorting:', 'user_sort', settings.user_sort, ['Hot', 'New', 'Top', 'Controversial']));
        body.appendChild(createMenu('Search Sorting:', 'search_sort', settings.search_sort, ['Relevance', 'Top', 'New', 'Comments']));
        body.appendChild(createMenu('Search in User Sorting:', 'author_search_sort', settings.author_search_sort, ['Relevance', 'Top', 'New', 'Comments']));
        body.appendChild(createMenu('Search in Flair Sorting:', 'flair_search_sort', settings.flair_search_sort, ['Relevance', 'Top', 'New', 'Comments']));
        body.appendChild(createMenu('Search in Self Sorting:', 'self_search_sort', settings.self_search_sort, ['Relevance', 'Top', 'New', 'Comments']));
        body.appendChild(createMenu('Search in SelfText Sorting:', 'selftext_search_sort', settings.selftext_search_sort, ['Relevance', 'Top', 'New', 'Comments']));
        body.appendChild(createMenu('Search in Site Sorting:', 'site_search_sort', settings.site_search_sort, ['Relevance', 'Top', 'New', 'Comments']));
        body.appendChild(createMenu('Search in Subreddit Sorting:', 'subreddit_search_sort', settings.subreddit_search_sort, ['Relevance', 'Top', 'New', 'Comments']));
        body.appendChild(createMenu('Search in Title Sorting:', 'title_search_sort', settings.title_search_sort, ['Relevance', 'Top', 'New', 'Comments']));
        body.appendChild(createMenu('Search in URL Sorting:', 'url_search_sort', settings.url_search_sort, ['Relevance', 'Top', 'New', 'Comments']));
        const reset = document.createElement('div');
        reset.id = 'settings-reset';
        reset.className = 'header custom-font';
        reset.textContent = 'Reset';
        reset.onclick = async function(event) {
          GM.deleteValues(['autoplay_videos', 'bottom_gap', 'comment_scroll', 'external_newtab', 'font_scale', 'nsfw_contents', 'preload_images', 'preload_videos',
                           'remember_search', 'search_checked', 'search_input', 'video_quality', 'comment_sort', 'post_sort', 'user_sort', 'search_sort', 'author_search_sort',
                           'flair_search_sort', 'self_search_sort', 'selftext_search_sort', 'site_search_sort', 'subreddit_search_sort', 'title_search_sort', 'url_search_sort']);
          Object.assign(settings, defaultSettings);
          container.remove();
          container = undefined;
          button.click();
        };
        const save = document.createElement('div');
        save.id = 'settings-save';
        save.className = 'header custom-font';
        save.textContent = 'Save & Reload';
        save.onclick = async function(event) {
          container.remove();
          container = undefined;
          Object.assign(settings, tempSettings);
          GM.setValues(tempSettings);
          if (!settings.remember_search) GM.deleteValue('search_input');

          const location = window.location;
          if (/^\/r\/[^/]+\/comments\/./.test(location.pathname)) {
            if (/[?&]sort=[^&]+/.test(location.search)) {
              location.href = location.origin + location.pathname.replace(/\/+$/, '') + location.search.replace(/(.)sort=[^&]+/, `$1sort=${settings.comment_sort}`) + location.hash;
            } else if (settings.comment_sort !== 'confidence') {
              location.href = location.origin + location.pathname.replace(/\/+$/, '') + location.search + (location.search ? `&sort=${settings.comment_sort}` : `?sort=${settings.comment_sort}`) + location.hash;
            } else {
              location.reload();
            }
          } else if (/^\/r\/[^/]+(?:\/hot|\/new|\/top|\/rising|\/controversial)?$/.test(location.pathname)) {
            location.href = location.origin + location.pathname.replace(/^(\/r\/[^/]+).*$/, '$1') + (settings.post_sort === 'hot' ? '' : `/${settings.post_sort}`) + location.search + location.hash;
          } else if (/^\/u\/[^/]+(?:\/overview|\/comments|\/submitted)?/.test(location.pathname)) {
            if (/[?&]sort=[^&]+/.test(location.search)) {
              location.href = location.origin + location.pathname.replace(/\/+$/, '') + location.search.replace(/(.)sort=[^&]+/, `$1sort=${settings.user_sort}`) + location.hash;
            } else if (settings.user_sort !== 'new') {
              location.href = location.origin + location.pathname.replace(/\/+$/, '') + location.search + (location.search ? `&sort=${settings.user_sort}` : `?sort=${settings.user_sort}`) + location.hash;
            } else {
              location.reload();
            }
          } else if (!settings.nsfw_contents) {
            if (!location.search) {
              location.href = location.origin + location.pathname + '?include_over_18=false' + location.hash;
            } else if (/include_over_18=[^&]+/.test(location.search)) {
              location.href = location.origin + location.pathname + location.search.replace(/include_over_18=[^&]+/, 'include_over_18=false') + location.hash;
            } else {
              location.href = location.origin + location.pathname + location.search + '&include_over_18=false' + location.hash;
            }
          } else {
            location.reload();
          }
        };

        content.appendChild(header);
        content.appendChild(body);
        content.appendChild(reset);
        content.appendChild(save);
        container.appendChild(content);
        document.body.appendChild(container);
      }
    };
    document.body.querySelector(':scope > .header')?.appendChild(button);
  };

  const clickGoesToComments = async function() {
    if (/^\/r\/[^/]+\/comments\/[^/]+\/(?!comment)/.test(window.location.pathname)) return;
    window.addEventListener('click', async event => {
      if (!event.target || !event.composed || event.target.id === 'previous-slide' || event.target.id === 'next-slide' ||
          event.target.id === 'expand-collapse' || event.target.classList.contains('post')) return;
      const anchor = event.composedPath().find(node => node.classList?.contains('inner-post'))?.querySelector(':scope > a[href^="/r/"]');
      if (!anchor || !/^\/r\/[^/]+\/comments\/./.test(anchor.pathname)) return;
      anchor.click();
      //window.console.log('GO TO COMMENTS');
    }, false);
  };

  // Highlighted at the wrong sort. It's "New" by default.
  const highlightUserSortMenu = async function() {
    if (!(/^\/u\/[^/]+(?:\/|\/overview|\/comments|\/submitted)?$/.test(window.location.pathname) && !substr(/[?&]sort=(hot|new|top|controversial)/, window.location.search))) return;
    document.body.querySelectorAll(':scope > .container > .content > .menu > a').forEach(async anchor => {
      if (!anchor.search) return;
      if (anchor.search === '?sort=new') { anchor.classList.add('focus'); return; }
      anchor.classList.remove('focus');
    });
  };

  // Sometimes, can't return to the previous images from the last image.
  // Sometimes, it just goes back and forth between next image and previous image.
  // Always, flickering/flashing when other element is getting focus when you're on the next image.
  const clickSlideImages = async function() {
    const onclick = async function(event) {
      let target = event.target.parentElement.firstElementChild;
      do {
        if (target.getAttribute('active') !== 'true') continue;
        if (event.target.id === 'previous-slide' && target.nextElementSibling instanceof HTMLLIElement) {
          target.setAttribute('active', false);
          target.nextElementSibling.setAttribute('active', true);
          break;
        }
        if (event.target.id === 'next-slide' && target.previousElementSibling instanceof HTMLLIElement) {
          target.setAttribute('active', false);
          target.previousElementSibling.setAttribute('active', true);
          break;
        }
        break;
      } while ((target = target.nextElementSibling));
    };
    document.body.querySelectorAll('ul.css-slider').forEach(async element => {
      element.querySelectorAll(':scope > li.slide').forEach(async (element, index, array) => {
        element.setAttribute('active', (index === array.length - 1 ? true : false));
        element.classList.remove('slide');
        element.removeAttribute('tabindex');
        element.firstElementChild?.classList.remove('slide-outer');
      });
      const previous = document.createElement('div');
      previous.id = 'previous-slide';
      previous.className = 'unselectable';
      previous.textContent = '❮❮';
      previous.onclick = onclick;
      const next = document.createElement('div');
      next.id = 'next-slide';
      next.className = 'unselectable';
      next.textContent = '❯❯';
      next.onclick = onclick;
      element.appendChild(previous);
      element.appendChild(next);
    })
  };

  const uppercasePreviousNext = async function() {
    if (/^\/r\/[^/]+\/comments\/./.test(window.location.pathname)) return;
    document.body.querySelectorAll(':scope > .container > .content > .nav > a.button').forEach(async anchor => {
      anchor.innerText = anchor.innerText.startsWith('<') ? 'Prev' : (anchor.innerText.endsWith('>') ? 'Next' : anchor.innerText);
    });
  };

  const addCommentCollapsers = async function() {
    if (!/^\/r\/[^/]+\/comments\/./.test(window.location.pathname)) return;
    const onclick = async function(event) {
      event.target?.parentElement?.parentElement?.querySelectorAll(':scope > :is(.comment-content, ul)').forEach(async element => {
        event.target.setAttribute('collapsed', (element.hidden = !element.hidden));
      });
    };
    document.body.querySelectorAll('.comment-info').forEach(async comment => {
      const collapser = document.createElement('div');
      collapser.id = 'collapser';
      collapser.textContent = '➤';
      collapser.onclick = onclick;
      comment.insertBefore(collapser, comment.firstElementChild);
    });
  };

  // Absolutely not perfect. There is no indicators at all.
  const highlightAdminsBotsAndMods = async function() {
    //if (!/^\/r\/[^/]+\/comments\/./.test(window.location.pathname)) return;
    const regex =  new RegExp('^\\/u\\/(?:[a-zA-Z0-9_-]+-ModTeam' +
    '|AutoModerator|RemindMeBot|SaveVideo|WhyNotCollegeBoard|WorldNewsMods|MAGIC_EYE_BOT' +
    '|flairassistant|kzreminderbot|pccmodbot|reddit|reputatorbot|sneakpeekbot|spotlight-app' +
    '|sticky-comments|topredditbot|trendingtattler' +
    ')$');
    document.body.querySelectorAll('a[href^="/u/"]').forEach(async anchor => {
      if (regex.test(anchor.pathname)) anchor.classList.add('moderator');
    });
  };

  const highlightOriginalPoster = async function() {
    if (!/^\/r\/[^/]+\/comments\/./.test(window.location.pathname)) return;
    const poster = document.body.querySelector(':scope > .container > .content > .post > .inner-post > .post-info > a[href^="/u/"]:not([href="/u/[deleted]"])');
    if (!poster) return;
    document.body.querySelectorAll(`.comment-info > a[href^="${poster.pathname}"]`).forEach(async anchor => anchor.classList.add('author'));
  };

  const hideAdminsBotsAndMods = async function() {
    if (!/^\/r\/[^/]+\/comments\/./.test(window.location.pathname)) return;
    document.body.querySelectorAll('.comment-info:has(a.moderator)').forEach(async comment => {
      if (comment.firstElementChild.id !== 'collapser') return;
      comment.firstElementChild.setAttribute('collapsed', true);
      comment.parentElement?.querySelectorAll(':scope > :is(.comment-content, ul)').forEach(async element => {
        element.hidden = true;
      });
    });
  };

  const replaceCommentLinkIcons = async function() {
    if (!/^\/r\/[^/]+\/comments\/./.test(window.location.pathname)) return;
    document.body.querySelectorAll(`.comment-info a[href^="${decodeURI(window.location.pathname).replace(/^(\/r\/[^/]+\/comments\/[^/]+\/).*$/, '$1')}"]`).forEach(async anchor => {
      anchor.classList.add('link');
      anchor.innerText = '➥';
    });
  };

  const seeMoreReplies = async function() {
    if (!/^\/r\/[^/]+\/comments\/./.test(window.location.pathname)) return;
    const onclick = async function(event) {
      if (!event.target) return;
      const anchor = event.target.parentElement.parentElement.firstElementChild.querySelector('a[href^="/r/"]');
      if (/^\/r\/[^/]+\/comments\/./.test(anchor)) return;
      anchor.click();
    };
    document.body.querySelectorAll(':is(.comment, .reply) > ul > p:last-child').forEach(async element => {
      if (element.innerText !== '...') return;
      element.classList.add('see-more-replies');
      element.innerText = 'See more replies to ' + element.parentElement.parentElement.firstElementChild.querySelector('a[href^="/u/"]').innerText;
      element.onclick = onclick;
    });
  };

  // navigate, reload, back_forward
  const autoScrollsToComments = async function() {
    if (!settings.comment_scroll || !/^\/r\/[^/]+\/comments\/./.test(window.location.pathname) ||
        window.performance.getEntriesByType('navigation')[0].type === 'back_forward') return;
    const comments = document.body.querySelector(':scope > .container > .content > .comments');
    const rect = comments?.getBoundingClientRect();
    if (!comments ||  window.scrollY >= rect.top) return;
    window.scrollTo({ top: rect.top || 0, left: 0, behavior: 'instant' }) // smooth, instant, auto
  };

  const replaceDomainsPosition = async function() {
    document.body.querySelectorAll('.inner-post').forEach(async element => {
      const url = element.querySelector(':scope > a.post-link')?.href;
      const anchor = element.querySelector(':scope > .post-info > a[href^="/domain/"]');
      if (!url || !anchor) return;
      anchor.href = url;
    });
  };

  // search: confidence, top, new, controversial, old
  const sortCommentLinks = async function() {
    if (settings.comment_sort === 'confidence') return;
    document.body.querySelectorAll('a[href^="/r/"]').forEach(async anchor => {
      if (!/^\/r\/[^/]+\/comments\/./.test(anchor.pathname)) return;
      anchor.href = anchor.pathname.replace(/\/+$/, '') + anchor.search + (anchor.search ? `&sort=${settings.comment_sort}` : `?sort=${settings.comment_sort}`) + anchor.hash;
    });
  };

  // pathname: hot, new, top, rising, controversial
  const sortPostLinks = async function() {
    if (settings.post_sort === 'hot') return;
    const anchor = document.body.querySelector(':scope > .header > a.main-link');
    if (anchor) {
      anchor.href = anchor.pathname + `${settings.post_sort}` + anchor.hash;
    }
    document.body.querySelectorAll('a[href^="/r/"]').forEach(async anchor => {
      if (!/^\/r\/[^/]+\/?$/.test(anchor.pathname)) return;
      anchor.href = anchor.pathname.replace(/\/+$/, '') + `/${settings.post_sort}` + anchor.search + anchor.hash;
    });
  };

  // They're sorted by "hot" but it's actually "new" by default.
  // pathname: overview, comments, submitted
  // search:   hot, new, top, controversial
  const sortUserLinks = async function() {
    if (settings.user_sort === 'new') return;
    document.body.querySelectorAll('a[href^="/u/"]').forEach(async anchor => {
      if (/[?&]sort=[^&]+/.test(anchor.search)) return;
      anchor.href = anchor.pathname.replace(/\/+$/, '') + anchor.search + (anchor.search ? `&sort=${settings.user_sort}` : `?sort=${settings.user_sort}`) + anchor.hash;
    });
  };

  // Enabled by default. Not yet supported by kddit to disable it.
  const disableNSFW = async function() {
    if (settings.nsfw_contents) return;
    // Home.
    const anchor = document.body.querySelector(':scope > .header > a.main-link');
    if (anchor) {
      anchor.href = anchor.pathname + '?include_over_18=false' + anchor.hash;
    }
    // Sort menu & Navigation.
    document.body.querySelectorAll(':scope > .container > .content > :is(.menu, .nav) > a').forEach(async anchor => {
      if (!anchor.search) {
        anchor.href = anchor.pathname + '?include_over_18=false' + anchor.hash;
      } else if (/include_over_18=[^&]+/.test(anchor.search)) {
        anchor.href = anchor.pathname + anchor.search.replace(/include_over_18=[^&]+/, 'include_over_18=false') + anchor.hash;
      } else {
        anchor.href = anchor.pathname + anchor.search + '&include_over_18=false' + anchor.hash;
      }
    });
  };

  // Some links are not proxied.
  const proxyExternalLinks = async function() {
    document.body.querySelectorAll('.md a').forEach(async anchor => {
      const domain = anchor.hostname.replace(/^(?:[^.]+\.)*([^.]+\.[^.]+)$/, '$1');
      if (domain !== 'reddit.com' && domain !== 'removeddit.com') return;
      anchor.href = anchor.pathname.replace(/(.)\/$/, '$1') + anchor.search + anchor.hash;
    });
  };

  const externalLinksInNewTab = async function() {
    if (!settings.external_newtab) return;
    const hostname = window.location.hostname;
    document.body.querySelectorAll('a').forEach(async anchor => {
      if (anchor.hostname === hostname) return;
      anchor.rel = 'noopener,noreferrer';
      anchor.target = '_blank';
    });
  };

  // Currently, kddit does not support m3u8. Just "in case" for the future.
  // 220, 270, 360, 480, 720, 1080
  // Videos are set to not preloaded by default.
  const autoplayPreloadAndVideoResolution = async function() {
    const autoplay = settings.autoplay_videos ? true : false;
    const preload = settings.preload_videos ? 'auto' : 'none'; // none, metadata, auto
    document.body.querySelectorAll('video').forEach(async video => {
      if (video.src && !video.src.startsWith('blob:') && Number(substr(/_([0-9]+)\.(?:mp4|m3u8)/, video.src)) > settings.video_quality) {
        const src = video.src;
        video.src = '';
        video.src = 'about:blank';
        video.removeAttribute('src');
        video.src = src.replace(/_[0-9]+\.(mp4|m3u8)/, `_${settings.video_quality}.$1`);
      }
      video.autoplay = autoplay;
      video.preload = preload;
    });
  };

  // Helper function.
  // Retry once. Ignores the image after a 60-second times out if there is no errors.
  const downloadImage = function(image, retries, timeout) {
    image.onerror = image.onload = null;
    image.removeAttribute('onerror');

    let loading, timeoutId, unlock;
    const abortController = new AbortController(), lock = new Promise(resolve => unlock = resolve), src = image.src;

    const finished = async function() {
      if (!loading) return;
      window.clearTimeout(timeoutId);
      abortController.abort();
      image.fetchPriority = 'low'; // If it is not yet done.
      image.loading = 'lazy';
      unlock();
    };

    image.addEventListener('error', event => {
      event.stopPropagation();
      event.stopImmediatePropagation();
      if (!loading) return;
      if (retries <= 0) { finished(); return; }
      image.src = '';
      image.src = 'about:blank';
      window.clearTimeout(timeoutId); // Errored within 60 seconds.
      timeoutId = window.setTimeout(finished, timeout);
      image.src = src;
      --retries;
    }, { capture: true, signal: abortController.signal });

    image.addEventListener('load', finished, { capture: true, signal: abortController.signal });

    image.src = '';
    image.src = 'about:blank';
    image.removeAttribute('src');
    image.decoding = 'async'; // sync, async, auto
    image.fetchPriority = 'high'; // low, high, auto
    image.loading = 'eager'; // eager, lazy
    loading = true;
    timeoutId = window.setTimeout(finished, timeout); // 60 seconds.
    image.src = src;

    return lock;
  };

  // naturalWidth == source's width, naturalHeight == source's height.
  // clientWidth == rendered width, clientHeight == rendered height.
  const preloadImages = async function() {
    if (!settings.preload_images) return;
    for (const img of document.body.querySelectorAll('img')) {
      if ((img.complete && img.naturalWidth && img.naturalHeight) || !img.src || img.src.startsWith('blob:')) continue;
      await downloadImage(img, 1, 60000); // Loads one by one.
    }
  };

  const redownloadBrokenImages = async function() {
    if (settings.preload_images) return;

    let loading, queue;
    const onerror = async function() {
      if (!this) return;
      this.onerror = null;
      (queue || (queue = [])).push(this);
      if (loading) return; // Waits one to finish first.
      loading = true;
      do {
        await downloadImage(queue[0], 1, 60000);
        delete queue[0];
        queue = queue.filter(image => !!image); // Trim.
      } while (0 < queue.length);
      loading = queue = undefined;
    };
    for (const img of document.body.querySelectorAll('img')) {
      if ((img.complete && img.naturalWidth !== 0 && img.naturalHeight !== 0) || !img.src || img.src.startsWith('blob:')) continue;
      if (!img.complete) {
        img.onerror = onerror;
      } else {
        await downloadImage(img, 1, 60000);
      }
    }
  };

  const doAll = async function() {
    hideLongPostsAndComments();
    addSearchBar().then(addSettings);
    clickGoesToComments();
    highlightUserSortMenu();
    clickSlideImages();
    uppercasePreviousNext();
    addCommentCollapsers();
    highlightAdminsBotsAndMods().then(hideAdminsBotsAndMods);
    highlightOriginalPoster();
    replaceCommentLinkIcons();
    seeMoreReplies();
    autoScrollsToComments();
    replaceDomainsPosition();
    sortCommentLinks();
    sortPostLinks();
    sortUserLinks();
    disableNSFW();
    proxyExternalLinks().then(externalLinksInNewTab);
    autoplayPreloadAndVideoResolution();
    preloadImages();
    redownloadBrokenImages();
  };

  if (document.readyState === 'loading') {
    window.addEventListener('DOMContentLoaded', doAll, true);
  } else {
    doAll();
  }

/*
  // FOR DEBUGGING.
  window.addEventListener('click', event => {
    window.console.log(event.target);
    if (!event.composed) return;
    let str = '';
    event.composedPath().forEach(element => {
      str += (element.tagName ? '<' + element.tagName.toLowerCase()
              + (element.id ? ' id="' + element.id + '"' : '')
              + (element.className ? ' class="' + element.className + '"/>\n' : '/>\n')
              : (element.constructor.name === 'HTMLDocument' ? 'document\n' : element.constructor.name.toLowerCase() + '\n'));
    });
    window.console.log(str);
  }, false);
*/

})();