Fixdit for Reddit Redesign

UIX enhancements for Reddit's 2018 redesign. We <3 the redesign. Filter content, view hover cards for subreddits, & more...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Fixdit for Reddit Redesign
// @namespace    http://tampermonkey.net/
// @version      0.6.1
// @description  UIX enhancements for Reddit's 2018 redesign. We <3 the redesign. Filter content, view hover cards for subreddits, & more...
// @author       scriptpost (u/postpics)
// @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
// @grant        GM_deleteValue
// @noframes
// ==/UserScript==

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

    // TODO:
    // Add a UI tweak to force comments to reload the page.
    // Add UI tweak to make submission links easier to click?
    // Extend debug: add button to reset storage.
    // Remove ability to toggle required features and options.
    // Add notification each time settings are changed.
    // Add mod menu to menu_hover options.
    // Find more observers to add.
    // Remove setIntervals from observers?
    // User tagging and highlighting (highlight with symbol?)
    // Extend observers for no_prefixes.
    // Test dismiss dialog+settings, then bring it back and save. Error?

    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);
      }

      set toggle(fn) {
        this.setting.toggle = fn;
      }


      // arg: object
      set options_toggle(functions) {
        this.options_triggers = functions;
      }

      redraw() {
        // Finds all the changed options and updates their appearance.
        // Also calls any provided callback functions.
        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');
              }
            });

            if (this.options_triggers.hasOwnProperty(oid) && this.setting.data.enabled) {
              this.options_triggers[oid](new_val);
            }
          }
        }
      }

      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_option_btn",
          "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 el = $('.fixd_options .fixd_switch:not(.fixd_option)[data-id="' + this.data.id + '"]')[0];
        if (this.data.enabled) {
          $(el).add(this.button).addClass('fixd_enabled');
        }
        else {
          $(el).add(this.button).removeClass('fixd_enabled');
        }
        if (this.toggle) {
          this.toggle(this.data.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];

      if (option.data.hidden) return;

      // 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');
      }
      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 {
          if (saved_data.value) {
            value = saved_data.value[idx];
          }
          else {
            value = data.value[idx];
          }
        }
        const item = $('<label>', {
          "html": '<input type="radio" name="choices" value="' + key + '"> ' + choice,
          "class": 'fixd_option_select'
        })[0];
        if (value && 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.dataset.clickId === '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 = [];

        if (!subscribers) return;
        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');

        if ($side.length) {
          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"
      });
      feature.add_option({
        id: "static_header",
        label: "Make the top bar stationary",
        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');
      };

      const static_header = (enabled) => {
        if (enabled === undefined || enabled) {
          document.querySelector('body > div #header').style.position = 'absolute';
        } else {
          document.querySelector('body > div #header').style.position = '';
        }
      };

      feature.options_toggle = {
        'static_header': static_header
      };

      if (feature.setting.data.enabled) {
        static_header(feature.options.static_header.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, "");
          let name = sub_name.replace("/", "");

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

          if (is_match(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 Child Comments",
        enabled: true
      });
      feature.add_option({
        id: 'auto',
        type: 'bool',
        label: 'Automatically collapse children',
        enabled: false
      });

      const auto_on = feature.options.auto.data.enabled;

      const init = function (is_mutate) {
        let comment = document.getElementsByClassName('Comment')[0];
        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 (auto_on && !is_mutate) {
            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);
          }

          for (var i = 0; i < comments_list.length; i++) {
            init_comment(comments_list[i], is_mutate);
          }
        }
      };

      const init_comment = function (item, is_mutate) {
        item.classList.add('fixd_comment_wrap');
        const is_comment = !!item.getElementsByClassName('Comment').length;
        const is_child = !item.getElementsByClassName('top-level').length;
        const is_thread = !!item.getElementsByClassName('threadline').length;
        const is_hidden = !!item.getElementsByClassName('icon-expand').length;

        if (is_comment && !is_child) {
          $(item).addClass('fixd_top-level');
          const $next = $(item).next();
          const next_is_child = $next.length && !$next[0].getElementsByClassName('top-level').length;
          const next_is_thread = $next && !!$next[0].getElementsByClassName('threadline').length;

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

            if (auto_on && !is_mutate) {
              btn_classList.push('fixd_active');
            }

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

            let $target = $(item).find('button:last');

            if (!$target.length) {
              $target = $(item).find('a:last');
            }

            $btn.insertAfter($target);
          }
        }
        if (auto_on && is_child && !is_mutate && !is_hidden) {
          item.classList.add('fixd_hidden');
        }
      };

      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[0].getElementsByClassName('top-level').length;
          const next_is_thread = !!$next[0].getElementsByClassName('threadline').length;
          if (next_is_child && next_is_thread) {
            if (is_active) {
              $next.removeClass('fixd_hidden');
            }
            else {
              $next.addClass('fixd_hidden');
            }
            if ($next.next().length) {
              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');
          }
        }
      };

      const handle_mutation = function (mutation) {
        init_comment(mutation.previousSibling, true);
      };

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

        Overlay_Observer.extend(function (self, mutations) {
          for (var i = 0; i < mutations.length; i++) {
            if (mutations[i].addedNodes.length) {
              init(true);
            }
          }
        });

        Comment_Observer.extend(function (self, mutations) {
          for (var a = 0; a < mutations.length; a++) {
            handle_mutation(mutations[a]);
          }
        });
      }

      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: 'Short', medium: 'Medium', long: 'Long' },
        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] || delay.medium);
            });
            $(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
      });

      const init = (enabled) => {
        if (enabled) {
          $('#fixd_indicators').show();
          document.body.classList.add('fixd_debug');
        }
        else {
          $('#fixd_indicators').hide();
          document.body.classList.remove('fixd_debug');
        }
      };

      feature.toggle = init;
      init(feature.setting.data.enabled);
      return feature;
    })();

    const _stylesheet = '<style type="text/css" id="fixdit">#fixd_settings .fixd_option_select input,#fixd_settings .fixd_switch input,#fixd_settings button{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;-moz-user-select:none;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_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;vertical-align:middle;margin-bottom:10px}#fixd_settings .fixd_option,#fixd_settings .fixd_option_btn,#fixd_settings .fixd_option_select,#fixd_settings .fixd_switch{display:block;margin:1px -12px 0;cursor:default;-moz-user-select:none;user-select:none;background:#e4e6e6}#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_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{position:absolute;top:0;left:0;bottom:0;border-radius:4px;background:hsla(180,2%,90%,1);right:0}#fixd_settings .fixd_option span{padding-left:.3em}#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 button{text-transform:uppercase;border:1px solid transparent;border-radius:2px}#fixd_settings button:hover{border:1px solid #666}#fixd_settings .fixd_btn_back,#fixd_settings .fixd_btn_cancel{font-weight:700;color:transparent;margin-right:12px;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;font-size:12px;font-weight:700}a+button.fixd_collapse{margin-left:10px}button.fixd_collapse_all{margin-left:20px;color:#a6a4a4;font-size:12px;font-weight:700;text-transform:uppercase}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_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_popup:not(.fixd_filterable) .fixd_popup_filter,body:not(.fixd_debug) .fixd_hidden{visibility:hidden;position:absolute}.fixd_popup .fixd_popup_created,.fixd_popup .fixd_popup_subs{font-weight:500}.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"}.fixd_tooltip{pointer-events:none}body.fixd_debug .fixd_hidden{opacity:.6}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));