Fixdit for Reddit

UIX enhancements for Reddit's 2018 redesign. We <3 the redesign.

当前为 2018-05-25 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Fixdit for Reddit
// @namespace    http://tampermonkey.net/
// @version      0.5.2.1
// @description  UIX enhancements for Reddit's 2018 redesign. We <3 the redesign.
// @author       scriptpost
// @match        https://www.reddit.com/*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues
// @noframes
// ==/UserScript==

(function ($, undefined) {
  $(function () {
    if (!$('#hamburgers').length) return; // probably using old site.

    class Feature {
      constructor(data) {
        this.loaded = JSON.parse(GM_getValue('features', '{}'));

        if (this.loaded.hasOwnProperty(data.id)) {
          data.enabled = this.loaded[data.id].enabled;
        }
        else {
          const db_entry = this.loaded;

          db_entry[data.id] = {
            enabled: data.enabled,
            options: {}
          };

          GM_setValue('features', JSON.stringify(db_entry));
          this.enabled = data.enabled;
        }

        this.feature = data;
        this.setting = Feature.add_setting(data);
        this.options = {};
      }

      static add_setting(data) {
        return new Setting(data);
      }

      add_option(option) {
        const loaded_options = this.loaded[this.feature.id].options;
        const is_stored = loaded_options.hasOwnProperty(option.id);

        if (is_stored) {
          // Modify passed object.
          option.enabled = loaded_options[option.id].enabled;

          if (loaded_options[option.id].hasOwnProperty('value')) {
            option.value = loaded_options[option.id].value;
          }
        }
        else {
          // Continue without modifying.
          const db_entry = this.loaded;
          db_entry[this.feature.id].options[option.id] = {};

          if (option.hasOwnProperty('value')) {
            db_entry[this.feature.id].options[option.id].value = option.value;
          }
          db_entry[this.feature.id].options[option.id].enabled = option.enabled;
          GM_setValue('features', JSON.stringify(db_entry));
          this.loaded = db_entry;
        }

        this.options[option.id] = new Option(option);
      }

      redraw() {
        // Finds all the changed options and updates their appearance.
        this.setting.redraw();
        for (var oid in this.options) {
          const new_val = this.loaded.options[oid].enabled;
          const current_val = this.options[oid].enabled;

          if (new_val !== current_val) {
            const fid = this.feature.id;
            this.options[oid].enabled = new_val;

            const panel = '.fixd_options[data-id="' + fid + '"]';
            const dialog = '.fixd_dialog[data-id="' + oid + '"]';
            const $btn = $(panel + ' .fixd_option[data-id="' + oid + '"]');
            const $switch = $(dialog + ' .fixd_switch[data-id="' + oid + '"]');

            $btn.add($switch).each((idx, el) => {
              if (el.classList.contains('fixd_enabled')) {
                el.classList.remove('fixd_enabled');
              }
              else {
                el.classList.add('fixd_enabled');
              }
            });
          }
        }
      }

      set update(data) {
        this.setting.data.enabled = data.enabled;
        this.loaded = data;
        this.redraw();
      }
    }

    class Setting {
      constructor(data) {
        this.data = data;

        this.button = $('<div>', {
          "text": data.label,
          "class": "fixd_setting",
          "data-click-id": "fixd_setting",
          "data-id": data.id
        })[0];

        if (data.enabled) {
          $(this.button).addClass('fixd_enabled');
        }
        if (!data.internal) {
          $('#fixd_settings .fixd_panel:not(.fixd_options)').append(this.button);
        }
      }

      redraw() {
        const toggle = $('.fixd_options .fixd_switch:not(.fixd_option)[data-id="' + this.data.id + '"]')[0];
        if (this.data.enabled) {
          $(toggle).add(this.button).addClass('fixd_enabled');
        }
        else {
          $(toggle).add(this.button).removeClass('fixd_enabled');
        }
      }
    }

    class Option {
      constructor(data) {
        this.enabled = data.enabled;
        this.data = data;
      }
    }

    /**
     * Creates an observer wrapper that can be extended from anywhere.
     * Each time an RO is extended, it disconnects the last observer
     * and adds a new one in its place with all the added functions.
     * When there's a mutation and a condition is met, it loops over
     * all the saved functions (from this.actions).
     */
    class Reddit_Observer {
      constructor(target) {
        this.target = target;
        this.actions = [];
      }

      set callback(callback) {
        this.basis = callback;
      }

      get records() {
        if (this.observer) {
          return this.observer.takeRecords();
        }
      }

      loop_all(mutations) {
        for (var i = 0; i < this.actions.length; i++) {
          this.actions[i](this, mutations);
        }
      }

      extend(fn) {
        this.actions.push(fn);
        if (this.target) {
          this.connect();
        }
      }

      connect(newTarget) {
        if (newTarget) {
          this.target = newTarget;
        }
        const self = this;
        const mutation = function (mutationsList) {
          self.basis(self, mutationsList);
        };
        if (this.observer) {
          this.observer.disconnect();
        }
        this.observer = new MutationObserver(mutation);
        this.observer.observe(this.target, { childList: true });
      }
    }

    const Utils = {
      format_date: {
        age: date => {
          // https://stackoverflow.com/a/23286781
          const diff_date = new Date(new Date() - date);
          let y = diff_date.toISOString().slice(0, 4) - 1970;
          let m = diff_date.getMonth() + 0;
          let d = diff_date.getDate();
          let result;
          if (y > 0) result = (y === 1) ? y + ' year' : y + ' years';
          else if (m > 0) result = (m === 1) ? m + ' month' : m + ' months';
          else result = (d === 1) ? d + ' day' : d + ' days';
          return result;
        }
      }
    };

    const get_reddit_data = function (kind, name) {
      return new Promise(function (resolve, reject) {
        let url;
        const key = kind + '_' + name;
        const cache = JSON.parse(GM_getValue('cache', '{}'));
        let ratelimit = JSON.parse(GM_getValue('ratelimit_get', '{}'));

        if (cache[key]) {
          resolve(cache[key]);
        }
        else if (!ratelimit.remaining || ratelimit.remaining > 150) {
          const req = new XMLHttpRequest();
          url = '/r/' + name + '/about.json';
          req.open('GET', url);

          req.onload = function () {
            if (req.status === 200) {
              const response = JSON.parse(this.response).data;
              let json_data = cache;

              ratelimit = {
                used: this.getResponseHeader('x-ratelimit-used'),
                remaining: this.getResponseHeader('x-ratelimit-remaining'),
                reset: this.getResponseHeader('x-ratelimit-reset')
              };

              json_data[key] = {
                name: response.display_name,
                title: response.title,
                subtitle: response.header_title,
                desc: response.public_description,
                created: response.created,
                subs: response.subscribers,
                subscriber: response.user_is_subscriber
              };

              GM_setValue('ratelimit_get', JSON.stringify(ratelimit));
              GM_setValue('cache', JSON.stringify(json_data));
              resolve(json_data[key]);
            }
            else {
              reject(Error(req.statusText));
            }
          };
          req.onerror = function () {
            reject(Error("Network Error"));
          };
          req.send();
        } else if (ratelimit.remaining) {
          reject(ratelimit.remaining + ' requests remaining.');
        }
      })
    };

    /**
     * Adds a visual indicator to show which mutations are occuring.
     */
    $('body').append($('<div id="fixd_indicators">').hide());
    class observer_indicator {
      constructor(name) {
        this.name = name;
        this.create();
      }

      create() {
        this.$el = $('<div class="fixd_observer_indicator" data-fixd-name="' + this.name + '">');
        $('#fixd_indicators').append(this.$el);
      }

      pulse() {
        this.$el.addClass('fixd_active');
        this.timeout = window.setTimeout(() => {
          this.$el.removeClass('fixd_active');
        }, 2000);
      }
    };

    /**
     * @param {string} html_tag Element to look inside.
     * @param {string} query Inner text to match against.
     * @param {Node} context Region to search.
     */
    var getElementByText = function (html_tag, query, context) {
      const result = document.evaluate(
        "//" + html_tag + "[contains(., '" + query + "')]",
        context, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
      return result;
    };

    const body_indicator = new observer_indicator('doc body');
    const viewframe_indicator = new observer_indicator('viewframe');
    const subreddit_indicator = new observer_indicator('subreddit');
    const overlay_indicator = new observer_indicator('overlay');
    const comment_indicator = new observer_indicator('comment');
    const comment_change_indicator = new observer_indicator('comment change');
    let post_indicator = new observer_indicator('post');

    /**
     * These special observers are defined according to Reddit's DOM behavior.
     * Each observer can be extended with .extend() and re-added with .connect().
     */
    const Body_Observer = new Reddit_Observer(document.body);
    const Viewframe_Observer = new Reddit_Observer($('#hamburgers + div')[0]);
    const Subreddit_Observer = new Reddit_Observer($('.Post').parent().parent().parent().parent().parent()[0]);
    const Overlay_Observer = new Reddit_Observer($('#hamburgers').parent().parent()[0]);
    const Comment_Observer = new Reddit_Observer($('.Comment').parent().parent().parent().parent()[0]);
    const Posts_Observer = new Reddit_Observer($('.Post').parent().parent().parent()[0]);

    const body_mutation_basis = function (self, mutations) {
      body_indicator.pulse();
      self.loop_all(mutations);
    };

    var viewframe_mutation_basis = function (self, mutations) {
      // When new data is fetched.
      viewframe_indicator.pulse();
      let ping = window.setInterval(() => {
        var $posts = $('.Post').parent().parent().parent();
        if ($posts.length) {
          window.clearInterval(ping);
          Subreddit_Observer.connect($posts.parent().parent()[0]);
          Posts_Observer.connect($posts[0]);
          Overlay_Observer.connect($('#hamburgers').parent().parent()[0]);
          self.loop_all(mutations);
        }
      }, 250);
    };

    var subreddit_mutation_basis = function (self, mutations) {
      // When loading from the cache.
      subreddit_indicator.pulse();
      let ping = window.setInterval(() => {
        var $posts = $('.Post').parent().parent().parent();
        if ($posts.length) {
          window.clearInterval(ping);
          Subreddit_Observer.connect($posts.parent()[0]);
          Posts_Observer.connect($posts[0]);
          self.loop_all(mutations);
        }
      }, 250);
    };

    const overlay_mutation_basis = function (self, mutationsList) {
      overlay_indicator.pulse();
      var ping = window.setInterval(() => {
        var $comments = $('#lightbox .Comment.top-level');
        if ($comments.length) {
          window.clearInterval(ping);
          Comment_Observer.connect($comments.parent().parent().parent().parent()[0]);
          self.loop_all(mutationsList);
        }
      }, 250);

      var timeout = window.setTimeout(() => {
        window.clearInterval(ping);
      }, 10000);
    };

    const comment_mutation_basis = function (self, mutations) {
      comment_indicator.pulse();
      self.loop_all(mutations);
    };

    var posts_mutation_basis = function (self, mutations) {
      post_indicator.pulse();
      self.loop_all(mutations);
    };

    Body_Observer.callback = body_mutation_basis;
    Viewframe_Observer.callback = viewframe_mutation_basis;
    Subreddit_Observer.callback = subreddit_mutation_basis;
    Overlay_Observer.callback = overlay_mutation_basis;
    Comment_Observer.callback = comment_mutation_basis;
    Posts_Observer.callback = posts_mutation_basis;

    const clear_cache = (() => {
      let ratelimit = JSON.parse(GM_getValue('ratelimit_get', '{}'));
      if (ratelimit.used !== undefined && ratelimit.used <= 1) {
        GM_setValue('cache', '{}');
      }
    })();

    const get_settings_form = function () {
      const layout = $('<div>', {
        "id": "fixd_settings"
      }).append($('<div>', {
        "class": "fixd_panel"
      }).append($('<h1>', {
        "text": "Fixdit Settings"
      })))[0];
      return layout;
    };

    const draw_options_panel = function (fid) {
      const settings = $('#fixd_settings')[0];
      const feature = FT[fid];

      const setting_toggle = $('<label>', {
        "html": '<input type="checkbox"> ' + feature.setting.data.label,
        "data-click-id": 'fixd_setting_toggle',
        "data-id": fid,
        "class": "fixd_switch"
      })[0];

      const panel = $('<div>', {
        "class": "fixd_panel fixd_options"
      })[0];

      const btn_back = $('<button>', {
        "data-click-id": "back",
        "class": "fixd_btn_back",
        "text": "Back"
      })[0];

      settings.appendChild(panel);

      panel.dataset.id = feature.setting.data.id;
      panel.appendChild(btn_back);
      panel.appendChild($('<h2>', {
        "text": 'Feature'
      })[0]);
      panel.appendChild(setting_toggle);

      if (feature.setting.data.enabled) {
        setting_toggle.querySelector('input').setAttribute('checked', 'checked');
        setting_toggle.classList.add('fixd_enabled');
      }

      for (var oid in feature.options) {
        draw_option_item(panel, feature.options[oid]);
      }
    };

    const draw_option_item = function (panel, option) {
      const label = document.createTextNode(option.data.label);
      const row = $('<label>', {
        "class": "fixd_option",
        "data-id": option.data.id
      })[0];

      // Will it be a checkbox or button?
      if (!option.data.type || option.data.type !== 'bool') {
        row.dataset.clickId = "fixd_option_btn";
        row.appendChild($('<div>', {
          "class": "fixd_option_btn",
          "text": option.data.label
        })[0]);
        row.classList.add('fixd_option_btn');
        row.innerText = option.data.label;
      }
      else {
        row.classList.add('fixd_switch');
        row.dataset.clickId = "fixd_option_toggle";
        row.innerHTML = '<input type="checkbox"> ' + option.data.label;
      }
      if (option.enabled) {
        let checkbox = row.querySelector('input');
        if (checkbox) {
          checkbox.setAttribute('checked', 'checked');
          row.classList.add('fixd_enabled');
        }
        row.classList.add('fixd_enabled');
      }
      panel.appendChild(row);
    };

    const append_option_selector = function (el, data, saved_data) {
      var idx = 0;
      for (var key in data.choices) {
        const choice = data.choices[key];
        let value;
        if (data.type === 'select') {
          value = saved_data.value;
        }
        else {
          value = saved_data.value[idx];
        }
        const item = $('<label>', {
          "html": '<input type="radio" name="choices" value="' + key + '"> ' + choice,
          "class": 'fixd_option_select'
        })[0];
        if (value === key) {
          item.querySelector('input').setAttribute('checked', 'checked');
          item.classList.add('fixd_enabled');
        }
        if (data.type === 'multi') {
          item.querySelector('input').setAttribute('type', 'checkbox');
        }
        $(el).append(item);
        idx++;
      };
    };

    const draw_option_dialog = function (oid) {
      const panel = $('#fixd_settings .fixd_options')[0];
      const fid = $(panel).data('id');
      const saved = JSON.parse(GM_getValue('features', '{}'));
      const data = FT[fid].options[oid].data;
      const saved_data = saved[fid].options[oid];

      if (data.type === 'enum') {
        saved_data.value.sort((a, b) => {
          return a.localeCompare(b, 'en', { 'sensitivity': 'base' });
        });
      }

      const dialog = panel.appendChild($('<div>', {
        "data-id": oid,
        "class": "fixd_dialog"
      })[0]);
      const dialog_content = dialog.appendChild($('<div>')[0]);
      const header = $('<div>', {
        "class": "fixd_settings_header"
      })[0];
      dialog_content.appendChild(header);
      header.appendChild($('<button>', {
        "data-click-id": "cancel",
        "class": "fixd_btn_cancel",
        "text": "Back"
      })[0]);
      header.appendChild($('<h3>', {
        "text": FT[fid].setting.data.label
      })[0]);
      const toggle = $('<label>', {
        "html": '<input type="checkbox"> ' + data.label,
        "data-click-id": 'fixd_option_toggle',
        "data-id": data.id,
        "class": "fixd_switch"
      })[0];
      dialog_content.appendChild(toggle);
      if (saved_data.enabled) {
        toggle.classList.add('fixd_enabled');
        toggle.querySelector('input').setAttribute('checked', 'checked');
      }
      if (data.description) {
        dialog_content.appendChild($('<div>', {
          "class": "fixd_description",
          "html": data.description
        })[0]);
      }
      if (data.type === 'enum') {
        dialog_content.appendChild($('<textarea>', {
          "data-click-id": "fixd_option_value",
          "text": saved_data.value.join('\n')
        })[0]);
      }
      else if (['select', 'multi'].includes(data.type)) {
        append_option_selector(dialog_content, data, saved_data);
      }
      const buttons = $('<div>', {
        "class": "fixd_settings_buttons"
      })[0];
      dialog_content.appendChild(buttons);
      buttons.appendChild($('<button>', {
        "data-click-id": "save",
        "class": "fixd_btn_save",
        "text": "Save"
      })[0]);
      $('#fixd_settings').addClass('fixd_expanded').append(dialog).css({});
    };

    const close_dialog = function () {
      $('#fixd_settings').removeClass('fixd_expanded');
      $('#fixd_settings .fixd_dialog').remove();
    };

    const handle_submit = function (ev) {
      const panel = $('#fixd_settings .fixd_options')[0];
      const fid = panel.dataset.id;
      const oid = document.getElementById('fixd_selected_option').dataset.id;
      const saved = JSON.parse(GM_getValue('features', '{}'));
      const feature = FT[fid];
      const option = feature.options[oid];
      let db_entry = saved;
      if (option.data.type === 'enum') {
        let value = $('#fixd_settings .fixd_dialog textarea').val();
        value = value.replace(/[^a-zA-Z\d\n#._-]/mg, "");
        db_entry[fid].options[oid].value = value.split('\n');
      }
      else if (['select', 'multi'].includes(option.data.type)) {
        const dialog = $('#fixd_settings .fixd_dialog')[0];
        const $inputs = $(dialog).find('input[name="choices"]');
        if (option.data.type === 'multi') {
          // Empty array before assigning new value.
          db_entry[fid].options[oid].value = [];
        }
        $inputs.each((idx, el) => {
          if (el.checked) {
            if (option.data.type === 'select') {
              db_entry[fid].options[oid].value = el.value;
              return false;
            }
            else {
              db_entry[fid].options[oid].value[idx] = el.value;
            }
          }
        });
      }
      GM_setValue('features', JSON.stringify(db_entry));
      close_dialog();
    };

    const handle_cancel = function (ev) {
      close_dialog();
    };

    const handle_back = function (ev) {
      $('#fixd_settings .fixd_options').remove();
      $('#fixd_settings .fixd_panel:not(.fixd_options)').show();
    };

    $('body').append(get_settings_form);

    document.addEventListener('click', ev => {
      const path = ev.composedPath();
      if (!path.includes($('#fixd_launch')[0]) && !path.includes($('#fixd_settings')[0])) {
        $('#fixd_settings, #fixd_launch').removeClass('fixd_active');
        return;
      }
      ev.stopImmediatePropagation();

      if (ev.target.dataset.clickId === 'fixd_launcher') {
        if ($(ev.target).hasClass('fixd_active')) {
          $('#fixd_launch, #fixd_settings').removeClass('fixd_active');
          close_dialog();
        }
        else {
          $('#fixd_launch, #fixd_settings').addClass('fixd_active');
          $('#fixd_settings .fixd_options').remove();
          $('#fixd_settings .fixd_panel:not(.fixd_options)').show();
        }
      }
      if (ev.target.dataset.clickId === 'fixd_setting') {
        $('#fixd_settings .fixd_panel:not(.fixd_options)').hide();
        draw_options_panel(ev.target.dataset.id);
      }
      if ($(ev.target).hasClass('fixd_option_btn')) {
        const oid = ev.target.dataset.id;
        let selected = document.getElementById('fixd_selected_option');
        if (selected) {
          selected.removeAttribute('id');
        }
        ev.target.id = 'fixd_selected_option';
        draw_option_dialog(oid);
      }
      if (ev.target.dataset.clickId === 'save') {
        handle_submit(ev);
      }
      if (ev.target.dataset.clickId === 'cancel') {
        handle_cancel(ev);
      }
      if (ev.target.dataset.clickId === 'back') {
        handle_back(ev);
      }
      if ($(ev.target).hasClass('fixd_switch') || $(ev.target.parentNode).hasClass('fixd_switch')) {
        let fid = $('#fixd_settings .fixd_options').data('id');
        let oid = ev.target.dataset.id || ev.target.parentNode.dataset.id;
        if (!ev.target.dataset.id) {
          // Checkbox has been clicked by user or label.
          handle_settings_switch(ev, fid, oid);
        }
      }
      if ($(ev.target).hasClass('fixd_option_select') ||
        $(ev.target.parentNode).hasClass('fixd_option_select')) {
        if (ev.target.tagName === 'INPUT') {
          if (ev.target.getAttribute('type') === 'radio') {
            $('#fixd_settings .fixd_dialog')
              .find('input[name="choices"]').parent().removeClass('fixd_enabled');
          }
          ev.target.parentNode.classList.toggle('fixd_enabled');
        }
      }
    });

    const handle_settings_switch = function (ev, fid, oid) {
      const saved = JSON.parse(GM_getValue('features', '{}'));
      const feature = FT[fid];
      const clicked = ev.target.dataset.clickId || ev.target.parentNode.dataset.clickId;

      let db_entry = saved;
      if (clicked === 'fixd_setting_toggle') {
        if (saved[fid].enabled) {
          db_entry[fid].enabled = false;
        }
        else {
          db_entry[fid].enabled = true;
        }
      }
      else {
        let option = feature.options[oid];
        if (saved[fid].options[oid].enabled) {
          db_entry[fid].options[oid].enabled = false;
        }
        else {
          db_entry[fid].options[oid].enabled = true;
        }
      }
      GM_setValue('features', JSON.stringify(db_entry));
      feature.update = db_entry[fid];
    }

    /**
     * Begin modules/features.
     */
    const FT = {};
    FT.ui_selectors = (() => {
      var feature = new Feature({
        id: "ui_selectors",
        label: "Add UI Selectors",
        enabled: true,
        internal: true
      });

      const do_subreddits_boxes = function (context) {
        const subscribers = getElementByText('p', 'subscribers', context);
        const targets = [];

        let target = subscribers.iterateNext();

        while (target) {
          if (!$('fixd--subreddits').find(target).length) {
            const item = target.parentNode.parentNode.parentNode;
            const img = item.querySelector('img');
            const svg = item.querySelector('svg');
            if (img || svg) {
              let $h3 = $(item).parent().parent().find('h3');
              if ($h3.length) {
                $(item).parent().parent().parent().parent().addClass('fixd--subreddits');
              }
            }
          }

          if (subscribers.invalidIteratorState) break;

          target = subscribers.iterateNext();
        }
      };

      var init = function (view) {
        let $content, $side;
        if (!view || view === 'normal') {
          $content = $($('.Post')[0]).parent().parent().parent().parent().parent();
          $side = $content.find('+ div');
          $('#hamburgers + div').addClass('fixd--page');
        }
        else if (view === 'overlay') {
          $content = $('#lightbox > div > div:nth-of-type(2)');
          $side = $content.find('> div:nth-of-type(2)');
        }

        $content.addClass('fixd--content');
        $side.addClass('fixd--side');

        const moderators_h3 = getElementByText('h3', 'Moderators', $side[0]).iterateNext();
        $(moderators_h3).parent().addClass('fixd--moderators');

        do_subreddits_boxes($side[0]);
      };

      if (feature.setting.data.enabled) {
        init();

        Overlay_Observer.extend(function (self, mutations) {
          init('overlay');
        });
        Viewframe_Observer.extend(function (self, mutations) {
          init();
        });
        Subreddit_Observer.extend(function (self, mutations) {
          init();
        });
      }

      return feature;
    })();

    FT.ui_tweaks = (() => {
      var feature = new Feature({
        id: "ui_tweaks",
        label: "UI Tweaks",
        enabled: true
      });
      feature.add_option({
        id: "no_prefix",
        label: "No prefixes for subreddits, users",
        type: "bool",
        enabled: false
      });
      feature.add_option({
        id: "no_blanks",
        label: "All links can open in current tab",
        type: "bool",
        enabled: false
      });
      feature.add_option({
        id: "override_vote_icons",
        label: "Disable custom vote icons",
        enabled: false,
        type: "bool"
      });

      const init_override_vote_icon = function (el) {
        if (!el.childNodes.length) {
          const computed_style = window.getComputedStyle(el);
          const bg_value = computed_style.getPropertyValue('background-image');
          const class_list = ['fixd_no_icon'];

          if (bg_value.includes('IconInactive')) {
            class_list.push('fixd_inactive');
          }
          else if (bg_value.includes('IconActive')) {
            class_list.push('fixd_active');
          }
          $(el).addClass(class_list.join(' '));
        }
      };

      const init_override_vote_icons = function (context) {
        const selector = 'button[data-click-id="upvote"], button[data-click-id="downvote"]';
        $(context).find(selector).each((idx, el) => {
          init_override_vote_icon(el);
        });
      };

      var strip_prefixes = function (collection) {
        if (!collection) {
          collection = $('.Post');
        }
        $(collection).each((idx, el) => {
          var $a = $(el).find('a[data-click-id="user"]');
          $a.each((idx, el) => {
            if (!el.children.length) {
              el.innerText = el.innerText.replace("u/", "");
            }
          });
        });

        $(collection).each((idx, el) => {
          var $a = $(el).find('a[data-click-id="subreddit"]');
          $a.each((idx, el) => {
            if (!el.children.length) {
              el.innerText = el.innerText.replace("r/", "");
            }
          });
        });
      };

      var remove_blanks = function () {
        $('a[target="_blank"]').removeAttr('target');
      };

      if (feature.setting.data.enabled) {
        Body_Observer.extend(function (self, mutations) {
          if (feature.options.override_vote_icons.data.enabled) {
            init_override_vote_icons(document.body);
          }
          if (feature.options.no_blanks.data.enabled) {
            remove_blanks();
          }
          if (feature.options.no_prefix.data.enabled) {
            strip_prefixes();
          }
        });

        $(document).click(ev => {

          if (feature.options.override_vote_icons.data.enabled) {
            if (['upvote', 'downvote'].includes(ev.target.dataset.clickId)) {
              init_override_vote_icons(ev.target.parentNode);
            }
          }
        });

        Posts_Observer.extend(function (self, mutations) {
          var collection = [];

          for (var record in mutations) {
            let added_nodes = mutations[record].addedNodes;
            if (added_nodes.length === 1) {
              collection.push(added_nodes[0]);
            }
          }
          if (collection.length) {
            if (feature.options.no_prefix.data.enabled) {
              strip_prefixes();
            }
            if (feature.options.no_blanks.data.enabled) {
              remove_blanks();
            }
          }
        });
      }

      return feature;
    })();
    FT.filter_content = (function () {
      const feature = new Feature({
        id: "filter_content",
        label: "Filter Content",
        enabled: true
      });
      feature.add_option({
        id: "subreddits",
        label: "Posts by subreddit",
        type: "enum",
        enabled: true,
        description: "One subreddit name per line. No commas or slashes. Ignores search results.",
        value: []
      });
      feature.add_option({
        id: "users",
        label: "Posts by user",
        description: "One user name per line. No commas or slashes. Ignores search results.",
        type: "enum",
        enabled: false,
        value: []
      });
      feature.add_option({
        id: "comments",
        label: "Comments by user",
        description: "Hides comments and their replies if posted by one of these users \
        (click the + icon to unhide comments). One user name per line. No commas or slashes.",
        type: "enum",
        enabled: false,
        value: []
      });

      const blocked_subs = feature.options.subreddits.data;
      const blocked_submitters = feature.options.users.data;
      const blocked_comments = feature.options.comments.data;

      const regex = {
        url_subreddit: /.*\/r\//i,
        url_user: /.*\/user\//i
      };

      const is_match = function (query, list) {
        let result = list.findIndex(item => query.toUpperCase() === item.toUpperCase());
        if (result !== -1) {
          return true;
        }
      };

      const init_posts = function () {
        const $posts = $('.Post').parent().parent();

        const search_page = (() => {
          const h1 = $('.fixd--page h1')[0];
          const child = (h1) ? h1.firstChild : undefined;

          if (child && child.nodeValue) {
            return child.nodeValue.includes('Search results for');
          }
        })();

        if ($posts.length > 1 && !search_page) {
          // If length is 1, it's probably comments page.
          $posts.each((idx, el) => {
            filter_post(el);
          });
        }
      };

      const init_comments = function () {
        const $comments = $('.Comment').parent().parent().parent();

        $comments.each((idx, el) => {
          filter_comment(el);
        });
      };

      const filter_post = function (node) {
        let sub_link, user_link;
        if (feature.options.subreddits.data.enabled) {
          sub_link = $(node).find('.Post a[data-click-id="subreddit"]:first').attr('href');
        }
        if (feature.options.users.data.enabled) {
          user_link = $(node).find('.Post a[data-click-id="user"]:first').attr('href');
        }

        if (sub_link) {
          let sub_name = sub_link.replace(regex.url_subreddit, "");
          sub_name = sub_name.replace("/", "");

          if (is_match(sub_name, blocked_subs.value)) {
            node.classList.add('fixd_hidden');
          }
        }
        if (user_link) {
          let user_name = user_link.replace(regex.url_user, "");
          user_name = user_name.replace("/", "");

          if (!node.classList.contains('fixd_hidden') && is_match(user_name, blocked_submitters.value)) {
            node.classList.add('fixd_hidden');
          }
        }
      };

      const filter_comment = function (node) {
        let user_link = $(node).find('.Comment > div:nth-of-type(2) > div > div > a:first').attr('href');

        if (!user_link) return;

        let user_name = user_link.replace(regex.url_user, "");
        user_name = user_name.replace("/", "");

        if (is_match(user_name, blocked_comments.value)) {
          node = node.querySelector('.Comment');
          node.classList.forEach(function (name) {
            // Gets the unique comment ID and finds its threadline.
            var c_id;
            var match = /^t1_/.exec(name);
            if (match) {
              c_id = match.input;
              $(node).parent().find('> div:not(.Comment) .' + c_id).click();
              return false; // end loop.
            }
          });
        }
      };

      const handle_posts_mutation = function (mutation) {
        const nodes_array = Array.from(mutation.addedNodes);

        nodes_array.forEach((node) => {
          filter_post(node);
        });
      };

      const handle_comments_mutation = function (mutations) {
        mutations.forEach(function (mutation) {
          for (var i = 0; i < mutation.addedNodes.length; i++) {
            const nodes_array = Array.from(mutation.addedNodes);

            nodes_array.forEach((node) => {
              filter_comment(node);
            });
          }
        });
      };

      if (feature.setting.data.enabled) {
        if (feature.options.comments.data.enabled) {
          init_comments();

          Overlay_Observer.extend(function (self, mutations) {
            init_comments();
          });

          Comment_Observer.extend(function (self, mutations) {
            handle_comments_mutation(mutations);
          });
        }
        if (feature.options.subreddits.data.enabled || feature.options.users.data.enabled) {
          init_posts();

          Posts_Observer.extend(function (self, mutations) {
            mutations.forEach(function (mutation) {
              for (var i = 0; i < mutation.addedNodes.length; i++) {
                handle_posts_mutation(mutation);
              }
            });
          });

          Viewframe_Observer.extend(function (self, mutations) {
            let $posts = $('.Post').parent().parent().parent();
            init_posts();
          });

          Subreddit_Observer.extend(function (self, mutations) {
            let $posts = $('.Post').parent().parent().parent();
            init_posts();
          });
        }
      }

      return feature;
    })();
    FT.subreddit_info = (function () {
      const feature = new Feature({
        id: "subreddit_info",
        label: "Subreddit Info Box",
        enabled: true
      });
      feature.add_option({
        id: 'delay',
        label: 'Popup delay',
        enabled: true,
        required: true,
        choices: {short: 'Short', medium: 'Medium', long: 'Long'},
        value: 'medium',
        type: 'select'
      });

      const delay = {
        short: 150,
        medium: 300,
        long: 600
      }

      const create_popup = function (data, ev) {
        const classes = ['fixd_popup'];
        const filter_btn_classes = ["fixd_popup_filter"];
        const saved = JSON.parse(GM_getValue('features', '{}'));
        const filter_data = saved.filter_content.options.subreddits;
        if (data.subscriber) {
          classes.push('fixd_subscriber');
        }
        if (saved.filter_content.enabled && filter_data.enabled) {
          classes.push('fixd_filterable');
          if (filter_data.value.includes(data.name)) {
            filter_btn_classes.push('fixd_active');
          }
        }
        let document_width = $('html')[0].offsetWidth;
        let target_offset = $(ev.target).offset().left;
        let offset_left = target_offset;
        if ((document_width - target_offset) < (document_width / 2)) {
          offset_left -= 240;
        }
        const $box = $('<div>', {
          "id": "fixd_popup_subreddit",
          "class": classes.join(' '),
          "data-id": data.name
        }).css({
          top: $(ev.target).offset().top + ev.target.offsetHeight,
          left: offset_left
        })
          .append($('<div>')
            .append($('<h2>', {
              "class": "fixd_popup_name",
              "text": data.name
            }))
            .append($('<div>', {
              "class": "fixd_popup_created",
              "html": data.created
            }))
            .append($('<div>', {
              "class": "fixd_popup_subs",
              "html": '<span>' + data.subs + '</span> Subscribers'
            }))
            .append($('<button>', {
              "class": filter_btn_classes.join(' '),
              "text": 'Filter'
            }))
          )
          .append($('<div>')
            .append($('<div>', {
              "class": "fixd_popup_title",
              "text": data.title
            }))
            .append($('<div>', {
              "class": "fixd_popup_subtitle",
              "text": data.subtitle
            }))
            .append($('<div>', {
              "class": "fixd_popup_desc",
              "html": data.desc
            }))
          );
        return $box;
      };

      const display_popup = function (ev) {
        let name = ev.target.getAttribute('href').split('/')[2];
        get_reddit_data('t5', name).then((data) => {
          const date_created = new Date(data.created * 1000);
          const formatted = {
            name: data.name,
            title: data.title,
            subtitle: data.subtitle,
            created: Utils.format_date.age(date_created),
            subs: data.subs.toLocaleString(),
            subscriber: data.subscriber,
            desc: data.desc
          };
          const $box = create_popup(formatted, ev);
          $('body').append($box);
        }, function (error) {
          console.warn("Error retrieving subreddit info popup.", error);
        });
      };

      if (feature.setting.data.enabled) {
        let tmo_close;
        const init = ($el, open) => {
          let tmo_open;
          $el.on('mouseover', ev => {
            if (ev.target.tagName === 'A') {
              let a = ev.target;
              if ((a.dataset.clickId === 'subreddit' ||
                $(a).parents('p, .md, .fixd--subreddits').length) &&
                a.getAttribute('href').startsWith('/r/')) {
                tmo_open = window.setTimeout(() => { open(ev) }, 500);
              }
            }

          }).on('mouseout', ev => {
            window.clearTimeout(tmo_open);

            if ($('#fixd_popup_subreddit').length) {
              tmo_close = window.setTimeout(() => {
                $('.fixd_popup').detach();
              }, delay[feature.options.delay.data.value] || delay.medium);
            }

            $('#fixd_popup_subreddit').on('mouseover', ev => {
              window.clearTimeout(tmo_close);
            }).on('mouseout', ev => {
              const $popup = $('#fixd_popup_subreddit');
              if ($popup.length) {
                if (ev.target !== $popup[0] && !$(ev.target).parents($popup[0])) {
                  $('.fixd_popup').detach();
                }
              }
            });
          });
        };
        init($(document), (ev) => {
          display_popup(ev);
        });
        $(document).on('click', ev => {
          if (ev.target.classList.contains('fixd_popup_filter')) {
            const saved = JSON.parse(GM_getValue('features', '{}'));
            const name = $('#fixd_popup_subreddit').data('id');
            let db_entry = saved;
            const arr = saved.filter_content.options.subreddits.value;
            if (ev.target.classList.contains('fixd_active')) {
              const idx = arr.indexOf(name);
              db_entry.filter_content.options.subreddits.value.splice(idx);
              ev.target.classList.remove('fixd_active');
            }
            else {
              db_entry.filter_content.options.subreddits.value.push(name);
              ev.target.classList.add('fixd_active');
            }
            GM_setValue('features', JSON.stringify(db_entry));
          }
        });
      }

      return feature;
    })();
    FT.comments_collapse = (function () {
      const feature = new Feature({
        id: "comments_collapse",
        label: "Collapsible Comments",
        enabled: true
      });

      feature.add_option({
        id: 'auto',
        type: 'bool',
        label: 'Automatically collapse comments',
        enabled: false
      });

      const init = function () {
        let comment = document.querySelector('.Comment');
        let comments_list;

        if (comment) {
          comments_list = comment.parentNode.parentNode.parentNode.parentNode.childNodes;

          const sort_picker = $('#CommentSort--SortPicker').parent()[0];
          const btn_all_classList = ['fixd_collapse_all'];

          if (feature.options.auto.data.enabled) {
            btn_all_classList.push('fixd_active');
          }

          const $btn_all = $('<button>', {
            "text": "children",
            "class": btn_all_classList.join(' ')
          });

          if (sort_picker) {
            $(sort_picker).append($btn_all);
          }

          $(comments_list).each((idx, el) => {
            init_comment(el);
          });
        }
      };

      const init_comment = function (item) {
        const is_comment = !!$(item).find('.Comment').length;
        const is_top_level_comment = !!$(item).find('.Comment.top-level').length;
        const is_next = $(item).next().length;
        const next_is_child = is_next && !$(item).next().find('.Comment.top-level').length;
        const is_hidden = !!$(item).find('.Comment > button .icon-expand').length;

        $(item).addClass('fixd_comment_wrap');

        if (!is_comment) {
          // Adds helper class to the final 'more comments' button.
          const is_nested = !!$(item).find('> div > div > div:first').children().length;

          if (!is_nested) {
            $(item).addClass('fixd_more_comments');
            $(item).find('> div > div > div:nth-of-type(2)').addClass('fixd_top-level');
          }
        }

        let auto_threshold = 2

        if (is_top_level_comment) {
          $(item).addClass('fixd_top-level');

          if (next_is_child && !is_hidden && !$(item).find('.fixd_collapse').length) {
            const btn_classList = ['fixd_collapse'];

            if (feature.options.auto.data.enabled && $('.fixd_top-level').length >= auto_threshold) {
              btn_classList.push('fixd_active');
            }

            const $btn = $('<button>', {
              "text": "children",
              "class": btn_classList.join(' ')
            });

            $btn.insertAfter($(item).find('button:last'));
          }
        }
        else if (feature.options.auto.data.enabled && $('.fixd_top-level').length >= auto_threshold) {
          if (!$(item).hasClass('fixd_more_comments')) {
            item.classList.add('fixd_hidden');
          }
        }
      };

      const handle_mutation = function (mutation) {
        const nodes_array = Array.from(mutation.addedNodes);
        let prev = mutation.previousSibling;

        nodes_array.forEach((node) => {
          init_comment(prev);
        }, prev);

        const $top_level = $('.fixd_top-level');
        if (feature.options.auto.data.enabled && $top_level.length > 10) {
          $top_level.each((idx, el) => {
            $(el).find('.Comment .fixd_collapse:not(.fixd_active)').click();
          });
        }
      };

      const toggle_visibility = function (clicked) {
        let wrap = $(clicked).parents('.fixd_comment_wrap')[0];
        let is_active = $(clicked).hasClass('fixd_active');

        const check_next = function ($next) {
          const next_is_child = !$next.hasClass('fixd_top-level');
          const next_is_load_more = $next.hasClass('fixd_more_comments');
          if ($next.length && next_is_child && !next_is_load_more) {
            if (is_active) {
              $next.removeClass('fixd_hidden');
            }
            else {
              $next.addClass('fixd_hidden');
            }
            check_next($next.next());
          }
        };

        check_next($(wrap).next());

        if (is_active) {
          $(clicked).removeClass('fixd_active');
        } else {
          $(clicked).addClass('fixd_active');
        }
      };

      const handle_click = function (ev) {
        if ($(ev.target).hasClass('fixd_collapse')) {
          ev.stopImmediatePropagation();
          toggle_visibility(ev.target);
        }
        else if ($(ev.target).hasClass('fixd_collapse_all')) {
          ev.stopImmediatePropagation();
          if ($(ev.target).hasClass('fixd_active')) {
            $('.fixd_collapse.fixd_active').click();
          }
          else {
            $('.fixd_collapse:not(.fixd_active)').click();
          }
          if ($('.fixd_collapse:not(.fixd_active)').length) {
            $(ev.target).removeClass('fixd_active');
          }
          else {
            $(ev.target).addClass('fixd_active');
          }
        }
      };

      if (feature.setting.data.enabled) {
        init();
        $(document).on('click', handle_click);

        Overlay_Observer.extend(function (self, mutations) {
          init();
        });

        Comment_Observer.extend(function (self, mutations) {
          mutations.forEach(function (mutation) {
            for (var i = 0; i < mutation.addedNodes.length; i++) {
              handle_mutation(mutation);
            }
          });
        });
      }

      return feature;
    })();
    FT.menu_hover = (function () {
      var feature = new Feature({
        id: "menu_hover",
        label: "Hover to open menus",
        enabled: false
      });
      feature.add_option({
        id: 'menus',
        type: 'multi',
        label: "Choose menus",
        enabled: true,
        choices: { sortpicker: 'Sort Posts', user: 'User dropdown' },
        value: ["sortpicker", "user"]
      });
      feature.add_option({
        id: 'delay',
        type: 'select',
        label: "Delay",
        enabled: true,
        requires: "menus",
        choices: { short: 'Fast', medium: 'Medium', long: 'Slow' },
        value: 'medium'
      });

      const menus_val = feature.options.menus.data.value;
      const menus = {
        sortpicker: '#ListingSort--SortPicker',
        user: '#USER_DROPDOWN_ID'
      }

      const delay_val = feature.options.delay.data.value;
      const delay = {
        short: 150,
        medium: 300,
        long: 600
      }

      if (feature.setting.data.enabled) {
        if (feature.options.menus.data.enabled) {

          var add_menu_listeners = function (selector) {
            var commit_timeout;
            $(selector).on('mouseover', function (ev) {
              commit_timeout = window.setTimeout(() => {
                ev.target.click();
              }, delay[delay_val]);
            });
            $(selector).on('mouseout', function (ev) {
              window.clearTimeout(commit_timeout);
            });
          };

          for (var i = 0; i < menus_val.length; i++) {
            add_menu_listeners(menus[menus_val[i]]);
          }
        }
      }

      return feature;
    })();

    $('body').append('<div id="fixd_launch" data-click-id="fixd_launcher"></div>');

    FT.remindme = (function () {
      const feature = new Feature({
        id: "remindme",
        label: "Remind Me",
        enabled: false,
        internal: true
      });

      feature.add_option({
        id: "post",
        label: "Posts",
        enabled: false,
        type: "enum"
      });

      feature.add_option({
        id: "comment",
        label: "Comments",
        enabled: false,
        type: "enum"
      });

      const show_menu = function (ev, target, menu) {
        menu.display(ev, target, 'left');
      };

      if (feature.setting.data.enabled) {
      }

      return feature;
    })();
    FT.debug = (function () {
      var feature = new Feature({
        id: "debug",
        label: "Debug Fixdit",
        enabled: false
      });

      if (feature.setting.data.enabled) {
        $('#fixd_indicators').show();
      }

      return feature;
    })();

    const _stylesheet = '<style type="text/css" id="fixdit">#fixd_settings .fixd_option_select input,#fixd_settings .fixd_switch input,#fixd_settings h2,#fixd_settings h3,#fixd_settings_panel>div>label input{vertical-align:middle}#fixd_launch{box-sizing:border-box;position:fixed;top:2px;right:2px;width:24px;height:24px;z-index:100;text-align:center;border-radius:50%;border:2px solid hsla(70,0%,100%,.5);border-left-color:hsla(70,0%,0%,1);border-right-color:hsla(70,0%,0%,1);background:0 0;cursor:pointer;user-select:none}#fixd_launch.fixd_active{border-color:hsla(70,0%,100%,.5);border-top-color:hsla(70,0%,0%,1);border-bottom-color:hsla(70,0%,0%,1)}#fixd_launch:not(.fixd_active):hover:before{content:"Fixdit settings...";display:inline-block;padding:6px 12px;font-size:80%;color:#fff;position:absolute;right:calc(100% + 12px);white-space:nowrap;background:hsla(0,0%,0%,.8);border-radius:2px;pointer-events:none}#fixd_settings{position:fixed;top:0;right:0;opacity:0;z-index:101;padding:24px 12px;background:hsla(180,2%,90%,.95);border-radius:3px;box-shadow:0 5px 10px hsla(0,0%,0%,.25),0 0 3px hsla(0,0%,0%,.25);font-size:80%;width:300px;visibility:hidden;overflow:hidden;transition:visibility .2s,height .2s,bottom .2s,left .2s,top .2s,right .2s,opacity .2s}#fixd_settings .fixd_option_btn,#fixd_settings .fixd_option_select,#fixd_settings .fixd_setting,#fixd_settings .fixd_switch{background:0 0}#fixd_settings.fixd_active{visibility:visible;opacity:1;top:5px;right:24px}#fixd_settings.fixd_expanded{bottom:5px}.fixd_description{margin:12px 0 0;line-height:1.3}#fixd_settings h1{font-size:140%;font-weight:400;margin:0 0 .5em}#fixd_settings h2,#fixd_settings h3{font-size:120%;font-weight:400;display:inline-block;margin-bottom:10px}#fixd_settings .fixd_option,#fixd_settings .fixd_option_select,#fixd_settings .fixd_setting,#fixd_settings .fixd_switch{display:block;margin:1px -12px 0;cursor:default;user-select:none}#fixd_settings .fixd_option_btn{margin:1px -12px;cursor:default}#fixd_settings .fixd_option_btn:after{content:" >";color:#999;font-weight:700;float:right}#fixd_settings .fixd_option_btn:hover:after{color:#000}#fixd_settings .fixd_option_btn:hover,#fixd_settings .fixd_setting:hover{outline:hsla(0,0%,0%,.3) solid 1px}#fixd_settings .fixd_option_btn,#fixd_settings .fixd_option_select,#fixd_settings .fixd_setting,#fixd_settings .fixd_switch{padding:10px 16px}#fixd_settings .fixd_enabled{background:#fff}#fixd_settings .fixd_switch:not(.fixd_option){font-size:120%;margin-bottom:8px;background:#f2f2f2}#fixd_settings .fixd_switch.fixd_enabled:not(.fixd_option){color:#000;background:#fff}#fixd_settings .fixd_dialog,#fixd_settings .fixd_dialog>div{top:0;left:0;bottom:0;border-radius:4px;background:hsla(180,2%,90%,1);position:absolute;right:0}#fixd_settings .fixd_option span{padding-left:.3em}#fixd_settings_panel>div:last-child label{border-bottom:1px solid #e5e5e5}#fixd_settings_panel>div>label{margin:0 -12px;padding:8px 12px;border-top:1px solid #e5e5e5;display:block;user-select:none}#fixd_settings .fixd_dialog{content:""}#fixd_settings .fixd_dialog>div{padding:24px 12px;display:flex;flex-direction:column}.fixd_dialog textarea{box-sizing:border-box;min-width:100%;max-width:100%;min-height:50px;max-height:100%;border:1px solid #fff;flex:1;margin-top:12px}.fixd_settings_buttons{margin-top:12px}#fixd_settings .fixd_btn,#fixd_settings button{display:inline-block;margin:0 12px 0 0;padding:6px 10px;cursor:pointer;text-align:center;text-transform:uppercase;vertical-align:middle;background:#fff;border-radius:2px;border:1px solid transparent}#fixd_settings .fixd_btn{margin:0;border-radius:50%;width:1.75em;height:1.75em;padding:0;position:absolute;right:0;top:3px}#fixd_settings .fixd_btn,#fixd_settings .fixd_btn_primary{border:1px solid #ccc}#fixd_settings .fixd_btn:hover,#fixd_settings .fixd_btn_primary:hover,#fixd_settings button:hover{border:1px solid #666}#fixd_settings .fixd_btn_back,#fixd_settings .fixd_btn_cancel{font-weight:700;color:transparent;padding:0;overflow:hidden;width:30px;height:30px;line-height:30px;margin-bottom:10px;border:1px solid #ccc;background:0 0}.fixd_btn_back:before,.fixd_btn_cancel:before{color:#000;content:"< "}#fixd_settings .fixd_btn_save{color:#fff;padding:8px 24px;background:#0087cc}button.fixd_collapse,button.fixd_collapse_all{cursor:pointer;color:inherit;display:inline-block;background:0 0;outline:0}button.fixd_collapse_all{margin-left:20px;color:#a6a4a4;font-size:12px;font-weight:700;text-transform:uppercase}.fixd_menu>div,.fixd_popup .fixd_popup_created,.fixd_popup .fixd_popup_subs{font-weight:500}button.fixd_collapse:before,button.fixd_collapse_all:before{content:"Hide "}button.fixd_collapse:after,button.fixd_collapse_all:after{content:" <<"}button.fixd_collapse.fixd_active:before,button.fixd_collapse_all.fixd_active:before{content:"Show "}button.fixd_collapse.fixd_active:after,button.fixd_collapse_all.fixd_active:after{content:" >"}#fixd_indicators{position:fixed;top:40px;right:0;z-index:100}.fixd_observer_indicator{box-sizing:border-box;margin:5px 0 0;width:20px;height:15px;border:1px solid hsla(0,0%,0%,.2);background:hsla(0,0%,50%,.2);transition:all .3s}.fixd_observer_indicator.fixd_active{border-color:hsla(0,0%,0%,1);background:hsla(0,0%,100%,1)}.fixd_observer_indicator.fixd_active:before,.fixd_observer_indicator:hover:before{content:attr(data-fixd-name);display:block;position:absolute;margin-top:-1px;right:100%;white-space:nowrap;color:#fff;font-size:11px;padding:3px 6px;background:hsla(0,0%,0%,.4);pointer-events:none}.fixd_observer_indicator:hover:before{background:hsla(0,0%,0%,.8)}.fixd--subreddits img,.fixd--subreddits svg{display:none}.fixd--subreddits a,.fixd--subreddits p{font-size:80%;line-height:1.5;width:auto}.fixd_menu{box-shadow:rgba(28,28,28,.2) 0 2px 4px 0;color:#1c1c1c;position:absolute;background-color:#fff;margin-top:0;border-radius:4px;z-index:30}.fixd_menu>div:active,.fixd_menu>div:hover{background-color:#0079d3;color:#fff;fill:#fff;border-color:#0079d3}.fixd_menu>div{font-size:14px;line-height:18px;color:#0079d3;background-color:#fff;white-space:nowrap;cursor:pointer;border:1px solid #edeff1;border-bottom:0;padding:8px 16px 8px 8px}.fixd_popup{box-sizing:border-box;position:absolute;z-index:100;padding:12px;font-size:12px;border-radius:4px;border-top:4px solid #c1cfd6;color:#1c1c1c;background-color:#fff;box-shadow:rgba(0,0,0,.2) 0 1px 3px}#fixd_popup_subreddit.fixd_subscriber{border-top-color:#0076d1}.fixd_popup>div{float:left}.fixd_popup>div:nth-of-type(2){width:200px;margin-left:12px;padding-left:12px;border-left:1px solid #edeff1}.fixd_popup .fixd_popup_subs span,.fixd_popup h2{display:block;font-size:16px;font-weight:500;line-height:20px}.fixd_hidden,.fixd_popup:not(.fixd_filterable) .fixd_popup_filter{display:none}.fixd_popup .fixd_popup_subs{margin-top:12px}.fixd_popup h2:before{content:"r/"}.fixd_popup .fixd_popup_subtitle{margin-bottom:.5em;color:#7f7f7f}.fixd_popup .fixd_popup_desc{color:#7f7f7f;line-height:1.2}.fixd_popup .fixd_popup_desc,.fixd_popup .fixd_popup_title{margin:0 0 .5em}.fixd_popup_filter{font-size:12px;text-transform:uppercase;padding:8px 12px;margin-top:12px;border-radius:2px;color:#fff;border:1px solid transparent;background:#0076d1;cursor:pointer}.fixd_popup_filter.fixd_active{color:#0076d1;border:1px solid;background:#fff}.fixd_popup_filter.fixd_active:before{content:"un"}button.fixd_no_icon.fixd_active[data-click-id=upvote]{color:#f40!important}button.fixd_no_icon.fixd_active[data-click-id=downvote]{color:#7091ff!important}button.fixd_no_icon[data-click-id=upvote]:hover{color:#cc3600}button.fixd_no_icon[data-click-id=downvote]:hover{color:#5b75cc}button.fixd_no_icon[data-click-id=downvote]:before,button.fixd_no_icon[data-click-id=upvote]:before{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:redesignFont}button.fixd_no_icon[data-click-id=upvote]:before{content:"\F12A"}button.fixd_no_icon[data-click-id=downvote]:before{content:"\F107"}</style>';
    $('body').append(_stylesheet);

  });
})(window.jQuery.noConflict(true));