[kddit] Search Button & Settings Button & UI Modifications

Adds search and settings buttons and modifies some UIs.

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

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

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         [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);
*/

})();