FastTech Forum Enhancements

Improvements for the FastTech forums and store

// ==UserScript==
// @name        FastTech Forum Enhancements
// @namespace   ftil
// @description Improvements for the FastTech forums and store
// @include     https://*.fasttech.com/*
// @version     2.5.1
// @grant       GM_getValue
// @grant       GM_setValue
// @run-at      document-start
// @icon        data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAOVBMVEVMaXEega+SlJeSkpgggK+Tk5gfgq+SlJeSkpkegK8egK8fgK+Tk5d1jZ6SkpcfgLEegLCTlJgfgbBPYHHZAAAAEXRSTlMApS2cl95Qi1CMvOmzFGgsdPNojJcAAACSSURBVHgBzdJlAgIwCAbQb915/7vaumG3vt8DRuAmIwheJOGQ2ywCvs8qYMVgsFLZSuiBrWhc4jsD8cIDrgh++qDNDM49MHmP4+wDDuIPHyRrE04VXbBlqZQ0hdhmKqXQZwwQaogcKD4MEt+QDMGLo8BvHe3qQTwwAJJjgyajPu/9Dwoj5MmDky6SEAlElYPGTUsirRDoa4UEWgAAAABJRU5ErkJggg==
// ==/UserScript==
'use strict';
// Delay function calls until their preconditions have been met.
var block = (function() {
  var counters = { };

  return {
    get: function(name) { return counters[name].val !== 0; },

    block: function(name) {
      var c = counters[name];
      if (c === undefined)
        counters[name] = { val: 1, watches: [] };
      else if (c.val !== 0)
        c.val++;
    },

    unblock: function(name) {
      var c = counters[name];
      if (c.val !== 0)
        c.val--;
      if (c.val === 0) {
        var w = c.watches;
        while (w.length !== 0)
          w.shift()();
      }
    },

    watch: function(name, func) {
      var c = counters[name];
      if (c.val !== 0)
        c.watches.push(func);
      else
        func();
    },
  };
})();
/* Userscript settings:
 * This is just terrible.  GM_[sg]etValue is used to initialize settings since
 * it's shared across subdomains, while localStorage is used to notify other
 * tabs of new settings.
 */
var settings = (function() {
  var settings = { };
  var watches = { };
  var prefix = 'ignore_list:';
  var prere = new RegExp('^' + prefix + '(.+)');
  var have_ls = (function() {
    try {
      localStorage.getItem('test');
      return true;
    } catch (e) {
      return false;
    }
  })();

  // Unblock after execution of the script
  block.block('ready');

  function inner_set(name, value) {
    // Ugh.
    if (JSON.stringify(settings[name].value) === JSON.stringify(value))
      return false;
    settings[name].value = value;
    if (name in watches) {
      var w = watches[name];
      for (var i = 0; i < w.length; i++)
        w[i](name, value);
    }
    return true;
  }

  if (have_ls) {
    window.addEventListener('storage', function(e) {
      var set = e.key.match(prere);
      if (set !== null && set[1] in settings)
        inner_set(set[1], JSON.parse(e.newValue));
    });
  }

  function get_sync(name) {
    var val = GM_getValue(name, null);
    if (val !== null)
      return JSON.parse(val);
    else
      return settings[name].def;
  }

  return {
    get: function(name) { return settings[name].value; },
    get_sync: get_sync,

    set: function(name, value) {
      if (inner_set(name, value)) {
        var s = JSON.stringify(value);
        if (have_ls)
          localStorage.setItem(prefix + name, s);
        GM_setValue(name, s);
      }
    },

    all: function() { return settings; },

    register: function(name, desc, def, dep, opt) {
      settings[name] = {
        desc: desc,
        def: def,
        dep: dep,
        opt: opt,
      };
      inner_set(name, get_sync(name));
    },

    watch: function(name, func) {
      if (!(name in watches))
        watches[name] = [];
      watches[name].push(func);
      if (settings[name] !== undefined)
        func(name, settings[name].value);
    },

    update: function() { block.unblock('ready'); },
  };
})();
// Dynamic stylesheets
var styles = (function() {
  var urls = {};
  var txts = {};
  var elms = {};
  var active_elm;

  function try_gmgrt(name) {
    try {
      return GM_getResourceText('theme_' + name);
    } catch (e) {}
  }

  function apply() {
    var enable = settings.get('use_theme');
    var name = settings.get('theme_name');

    if (enable === true && name !== undefined) {
      if (active_elm !== undefined) {
        if (active_elm === elms[name])
          return;
        active_elm.disabled = true;
      }

      if (name in elms) {
        elms[name].disabled = false;
      } else {
        var e, u;
        var c = try_gmgrt(name);
        if (c === undefined || c === null) {
          u = urls[name];
          c = txts[name];
        }
        if (c !== undefined) {
          e = document.createElement('style');
          e.type = 'text/css';
          e.innerHTML = c;
        } else {
          e = document.createElement('link');
          e.rel = 'stylesheet';
          e.type = 'text/css';
          e.href = u;
        }
        document.head.appendChild(e);
        elms[name] = e;
      }

      active_elm = elms[name];
    } else if (active_elm !== undefined) {
      active_elm.disabled = true;
      active_elm = undefined;
    }
  }

  return {
    register: function(name, url, txt) {
      if (url !== undefined)
        urls[name] = url;
      if (txt !== undefined)
        txts[name] = txt;
    },

    update: function() {
      settings.watch('use_theme', apply);
      settings.watch('theme_name', apply);
    },
  };
})();
// Sanitize external values that may be fed into an innerHTML
var strip_tags = (function() {
  var div = document.createElement('div');
  return function(t) {
    div.innerHTML = t;
    return div.textContent;
  };
})();

// Case-insensitive indexOf (needed in case of case errors in user input)
function ci_indexof(a, n) {
  var l = n.toLowerCase();
  for (var i = 0; i < a.length; i++) {
    if (a[i].toLowerCase() === l)
      return i;
  }
  return -1;
}

// Case-insensitive attribute lookup
function ci_lookup(a, n) {
  var l = n.toLowerCase();
  for (var k in a) {
    if (k.toLowerCase() === l)
      return k;
  }
}
// Assorted generic handlers for user input.
var handlers = (function() {
  function Handler(funcs, elm, data, extra) {
    this.elm = elm;
    this.data = data;
    this.extra = extra;
    if (funcs.setting !== undefined)
      settings.watch(data, funcs.setting.bind(this));
    for (var k in funcs) {
      if (k.slice(0, 2) === 'on')
        elm.addEventListener(k.slice(2), funcs[k].bind(this));
    }
  }

  function handler(fs) {
    return function(e, d, x) { return new Handler(fs, e, d, x); };
  }

  return {
    generic: handler,

    ignore_toggle: handler({
      onclick: function() { ignore_list.set(this.data); }
    }),

    checkbox: handler({
      onchange: function() { settings.set(this.data, this.elm.checked); },
      setting: function(n, v) { this.elm.checked = v; }
    }),

    top_click: handler({
      onclick: function() { scrollTo(0, 0); }
    }),

    hide_toggle: handler({
      onclick: function() { unhide_posts.set(this.data); }
    }),
  };
})();
// Manage the state of the ignore list
function create_ignore_list(list_var) {
  // Use two stages so that filters can handle updates before being called
  var watches = { 'early': [], 'late': [] };
  var il = [];

  function fire_watches(user, state, idx) {
    ['early', 'late'].forEach(function(stage) {
      var w = watches[stage];
      for (var i = 0; i < w.length; i++)
        w[i](user, state, idx);
    });
  }

  function inner_set(user, state, idx) {
    // In order to support userscripts, we need to rebuild the list on every set
    if (settings.get_sync) {
      il = [];
      var l = settings.get_sync(list_var);
      for (var i = 0; i < l.length; i++)
        il.push(l[i]);
    }
    if (state)
      il.push(user);
    else
      il.splice(idx, 1);
    settings.set(list_var, il);
    fire_watches(user, state, idx);
  }

  settings.register(list_var, null, [], null);
  // Watch for ignorelist changes, and fire incremental watch events
  settings.watch(list_var, function(name, val) {
    var i, j;
    if (il.length === val.length)
      return;
    if (il.length === 0) {
      for (i = 0; i < val.length; i++)
        il.push(val[i]);
      fire_watches(il, true);
      return;
    }
    i = 0;
    j = 0;
    while (true) {
      if (il[i] === undefined && val[j] === undefined)
        break;
      if (il[i] !== val[j]) {
        var user, state, idx;
        if (il[i] === undefined) {
          // New entry @ end of list: user added
          user = val[j];
          il.push(user);
          state = true;
          idx = i;
        } else {
          // Missing entry inside list: user removed
          user = il[i];
          il.splice(i, 1);
          state = false;
          idx = i;
        }
        fire_watches(user, state, idx);
        continue;
      }
      i++;
      j++;
    }
  });

  return {
    // Get ignored state of user (true == ignored)
    get: function(user) { return (ci_indexof(il, user) !== -1); },

    // Set ignored state of user (undefined == toggle)
    set: function(user, state) {
      var idx = ci_indexof(il, user);
      if (state === undefined)
        state = (idx === -1);
      if ((state && (idx === -1)) || (!state && (idx !== -1)))
        inner_set(user, state, idx);
    },

    /* Watch the ignorelist for changes (stage == ('early'|'late')).
     * func may be called with a single user and a new state, or an array of
     * users (with implicit ignored state for all)
     */
    watch: function(stage, func) {
      watches[stage].push(func);
      if (il !== [])
        func(il, true, -1);
    },
  };
}
/* Core post filtering logic
 * post vars are expected to be in the format
 * { user: string, content_elm: element, post_elms: [element] }
 */
var post_filter = (function() {
  var posts;
  var watches = [];
  var filters;

  settings.register('hide_ignored', 'Hide posts by ignored users', true, null);
  block.block('filter');

  function update() {
    if (posts === undefined)
      return;

    for (var i = 0; i < posts.length; i++) {
      var post = posts[i];
      var hide = false;
      var j;
      for (j = 0; j < filters.length; j++) {
        var tmp = filters[j](post);
        if (tmp === true) {
          hide = true;
        } else if (tmp === false) {
          hide = false;
          break;
        }
      }

      var display = hide ? 'none' : '';
      for (j = 0; j < post.post_elms.length; j++)
        post.post_elms[j].style.display = display;

      for (j = 0; j < watches.length; j++)
        watches[j](post, hide);
    }

    block.unblock('filter');
  }

  function builtin() {
    filters = [];
    filters.unshift((function() {
      settings.watch('hide_ignored', post_filter.update);
      ignore_list.watch('late', post_filter.update);

      return function(post) {
        if (settings.get('hide_ignored') && ignore_list.get(post.user))
          return true;
      };
    })());
    unhide_posts = (function() {
      var posts = [];

      filters.unshift(function(post) {
        if (posts.indexOf(post) !== -1)
          return false;
      });

      return {
        // Set the unhide state of a post (true == unhide, undefined == toggle)
        set: function(post, state) {
          var idx = posts.indexOf(post);
          if (state === undefined)
            state = (idx === -1);
          if (state && idx === -1)
            posts.push(post);
          else if (!state && idx !== -1)
            posts.splice(idx, 1);
          else
            return;
          post_filter.update();
        }
      };
    })();
  }

  return {
    // Register a list of posts to be filtered
    register: function(list) {
      posts = list;
      update();
    },

    /* Add a new filter.  Filters return true (should hide), false (must not
     * hide), or undefined (no judgement).  Does not trigger update().
     */
    filter: function(func) {
      if (filters === undefined)
        builtin();
      filters.push(func);
    },
    // Run all filters again
    update: update,

    /* Call func with changes to the hidden status of each post (true ==
     * hidden).  Does not trigger update().
     */
    watch: function(func) { watches.push(func); },
  };
})();

var unhide_posts;
// Generic buttons to toggle ignored state of a user
var ignore_buttons = (function() {
  var posts;
  var inner_ign = '';
  var inner_unign = '';

  function update_btns(users, state) {
    var i;
    if (users instanceof Array) {
      for (i = 0; i < posts.length; i++) {
        posts[i].ignbtn.innerHTML =
            ci_indexof(users, posts[i].user) !== -1 ? inner_unign : inner_ign;
      }
    } else {
      var ul = users.toLowerCase();
      for (i = 0; i < posts.length; i++) {
        if (!ul.localeCompare(posts[i].user.toLowerCase()))
          posts[i].ignbtn.innerHTML = state ? inner_unign : inner_ign;
      }
    }
  }

  return {
    // Set the (HTML) format of the (un)ignore buttons
    set: function(ign, unign) {
      inner_ign = ign;
      inner_unign = unign;
    },

    // Register posts to have their ignore buttons handled
    register: function(p) {
      posts = p;
      for (var i = 0; i < p.length; i++)
        handlers.ignore_toggle(p[i].ignbtn, p[i].user);
      ignore_list.watch('late', update_btns);
    },
  };
})();
// Handle RES-style tagging of users
var user_tags = (function() {
  var posts;

  function update_badges(n, val) {
    for (var i = 0; i < posts.length; i++) {
      var post = posts[i];
      var k = ci_lookup(val, post.user);
      post.badge.innerHTML = k !== undefined ? val[k] : post.saved_badge;
    }
  }

  var tag_prompt = handlers.generic({ onclick: function(e) {
    e.preventDefault();
    e.stopPropagation();

    var user = this.data;
    var tmp =
        (settings.get_sync ? settings.get_sync : settings.get)('user_tags');
    var def = '';
    if (tmp[user] !== undefined)
      def = tmp[user];
    var tag = prompt('Tag for ' + user + ' or blank for default:', def);
    if (tag === null)
      return;
    tag = strip_tags(tag);

    // Ugh.
    var tags = { };
    for (var u in tmp)
      tags[u] = tmp[u];

    if (tags[user] !== undefined && tag === '')
      delete tags[user];
    else if (tag !== '')
      tags[user] = tag;
    settings.set('user_tags', tags);
  }});

  return {
    // Register posts to have their badges handled
    register: function(p) {
      posts = p;
      for (var i = 0; i < p.length; i++) {
        var post = p[i];
        post.saved_badge = post.badge.innerHTML;
        tag_prompt(post.badge, post.user);
      }
      settings.watch('user_tags', update_badges);
    },

    update: function() {
      settings.register('user_tags', null, { }, null);
    },
  };
})();
// Track the last viewed page and post count for threads
var last_viewed = (function() {
  var db;
  var store = 'thread_data';

  function GetCmd(args) { this.args = args; }
  GetCmd.prototype.mode = 'readonly';
  GetCmd.prototype.success = function(e) {
    var t = e.target.result;
    if (t !== undefined)
      this.args.f(this.args.e, t.lvp, t.lpn, t.lp);
  };
  function SetCmd(args) { this.args = args; }
  SetCmd.prototype.mode = 'readwrite';
  SetCmd.prototype.success = function(e) {
    var t = e.target.result;
    if (t === undefined)
      t = {id: this.args.id};
    t.lvt = this.args.lvt;
    t.lvp = this.args.lvp;
    if (t.lpn === undefined || t.lpn < this.args.lpn) {
      t.lpn = this.args.lpn;
      t.lp = this.args.lp;
    }
    this.os.put(t);
  };
  GetCmd.prototype.exec = SetCmd.prototype.exec = function() {
    this.os = db.transaction(store, this.mode).objectStore(store);
    this.os.get(this.args.id).onsuccess = this.success.bind(this);
  };

  // Accumulate commands and fire them once idb is open
  var cmds = [];
  function run_cmds() {
    if (db === undefined || db === null)
      return open_db();

    var cmd;
    while ((cmd = cmds.shift()) !== undefined)
      cmd.exec();
  }

  // Open idb and maybe remove old threads
  function open_db() {
    if (db === undefined)
      db = null;
    else
      return;
    var req = indexedDB.open('ignore_list', 2);
    req.onupgradeneeded = function(e) {
      var tmp = e.target.result;
      if (!tmp.objectStoreNames.contains(store)) {
        var os = tmp.createObjectStore(store, { keyPath: 'id' });
        os.createIndex('lvt', 'lvt');
      }
    };
    req.onsuccess = function(e) {
      db = e.target.result;
      run_cmds();

      /* Periodically scrub the database (FIXME this is awful & may run in
       * multiple tabs simultaneously)
       */
      settings.register('lv_scrub_time', null, 0, null);
      settings.watch('lv_scrub_time', function(n, v) {
        var now = Date.now();
        if (v + 24 * 60 * 60 * 1000 > now)
          return;

        var tr = db.transaction(store, 'readwrite');
        var os = tr.objectStore(store);
        os.index('lvt')
            .openCursor(IDBKeyRange.upperBound(now - 60 * 24 * 60 * 60 * 1000))
            .onsuccess = function(e) {
              var c = e.target.result;
              if (c) {
                os.delete(c.primaryKey);
                c.continue();
              } else {
                settings.set('lv_scrub_time', now);
              }
            };
      });
    };
  }

  return {
    /* Record a visit to thread_id, on page last_viewed_page, with up to
     * last_post_nr (of id last_post_id) visible.  Should be called on every
     * (thread) pageview.
     */
    set: function(thread_id, last_viewed_page, last_post_nr, last_post_id) {
      cmds.push(new SetCmd({id: thread_id, lvp: last_viewed_page,
        lpn: last_post_nr, lp: last_post_id}));
      run_cmds();
    },

    // Call func with the view history of thread_id (and pass extra as well)
    get: function(thread_id, func, extra) {
      cmds.push(new GetCmd({id: thread_id, f: func, e: extra}));
      run_cmds();
    },

    update: open_db,
  };
})();
// Generic settings menu
var settings_menu = (function() {
  function mangle_opts(opts) {
    var ret = '';
    for (var k in opts)
      ret = ret + '<option value="' + k + '">' + opts[k].name + '</option>';
    return ret;
  }

  var watch_disable = handlers.generic({ setting: function(n, v) {
    this.elm.disabled = v ^ this.extra;
  }});

  var select = handlers.generic({
    onchange: function() { settings.set(this.data, this.elm.value); },
    setting: function(n, v) { this.elm.value = v; }
  });

  return {
    /* Attach a settings menu to elm.  Menus is a list of submenus, in the form
     * [ { title: 'str' | undefined, vars: [ "name" ] } ]
     */
    register: function(elm, menus) {
      var s = settings.all();
      var d = document.createElement('div');
      var html = '';
      var vs = [];
      for (var i = 0; i < menus.length; i++) {
        var menu = menus[i];
        if (i !== 0)
          html += '<hr></hr>';
        if (menu.title !== undefined)
          html += '<b>' + menu.title + '</b>';
        for (var j = 0; j < menu.vars.length; j++) {
          var v = menu.vars[j];
          vs.push(v);
          var sv = s[v];
          if (sv.opt !== undefined) {
            html += '<div><select style="margin-left: 21px">' +
                mangle_opts(sv.opt) + '</select></div>';
          } else {
            html += '<div><input type="checkbox" id="ilcb_' + v + '"></input>' +
                '<label for="ilcb_' + v + '">' + sv.desc + '</label></div>';
          }
        }
      }
      d.innerHTML = html;
      var e = d.firstElementChild;
      var depre = /(!)?(.*)/;
      function h(e, v) {
        var sv = s[v];
        if (sv.opt !== undefined)
          select(e, v);
        else
          handlers.checkbox(e, v);
        var d = sv.dep;
        if (d) {
          var ds = sv.dep.match(depre);
          var i = !ds[1];
          d = ds[2];
          watch_disable(e, d, i);
        }
      }
      while (e !== null) {
        if (e.tagName === 'DIV')
          h(e.firstElementChild, vs.shift());
        e = e.nextElementSibling;
      }
      elm.appendChild(d);
    }
  };
})();
// Display a nice menu for managing the ignorelist
var manage_menu = (function() {
  var menu_list;
  var menu_elms = [];
  var template = document.createElement('tr');
  template.innerHTML =
      '<td></td><td align="right"><a class="itmremove"></a></td>';

  function create_elm(user) {
    var tr = template.cloneNode(true);
    tr.firstElementChild.innerHTML = user;
    var a = tr.lastElementChild.firstElementChild;
    a.href = 'javascript:void(0)';
    handlers.ignore_toggle(a, user);
    if (this.appendChild)
      this.appendChild(tr);
    return { user: user.toLowerCase(), elm: tr };
  }

  function lc_comp(a, b) {
    return a.toLowerCase().localeCompare(b.toLowerCase());
  }

  function update(user, state) {
    if (menu_list === undefined)
      return;
    if (user instanceof Array) {
      var frag = document.createDocumentFragment();
      menu_elms = user.sort(lc_comp).map(create_elm, frag);
      menu_list.appendChild(frag);
    } else {
      var lc = user.toLowerCase();
      var idx;
      if (state) {
        for (idx = 0; idx < menu_elms.length; idx++) {
          if (menu_elms[idx].user.localeCompare(lc) >= 0)
            break;
        }
        var elm = create_elm.call(0, user);
        menu_elms.splice(idx, 0, elm);
        if (idx + 1 < menu_elms.length) {
          menu_list.insertBefore(menu_elms[idx].elm, menu_elms[idx + 1].elm);
        } else {
          menu_list.appendChild(menu_elms[idx].elm);
        }
      } else {
        for (idx = 0; idx < menu_elms.length; idx++) {
          if (menu_elms[idx].user.localeCompare(lc) >= 0)
            break;
        }
        if (idx < menu_elms.length) {
          menu_elms[idx].elm.remove();
          menu_elms.splice(idx, 1);
        }
      }
    }
  }

  var addbox_enter = handlers.generic({
    onkeydown: function(k) {
      if (k.which !== 13)
        return true;

      k.stopPropagation();
      k.preventDefault();

      ignore_list.set(strip_tags(this.elm.value), true);
      this.elm.value = '';

      return false;
    }
  });

  var addbox_click = handlers.generic({
    onclick: function() {
      ignore_list.set(strip_tags(this.data.value), true);
      this.data.value = '';
    }
  });


  return {
    // Attach management elements to elm
    register: function(elm) {
      var tbl = document.createElement('table');
      tbl.className = 'itmanage';
      tbl.innerHTML = '<tbody><tr><td colspan="2">' +
          '<input type="text" placeholder="Add user..." width="auto">' +
          '</input> <div class="il_addbtn">Add</div></td></tr></tbody>';
      menu_list = tbl.firstElementChild;

      var td = menu_list.firstElementChild.firstElementChild;
      addbox_enter(td.firstElementChild);
      addbox_click(td.lastElementChild, td.firstElementChild);

      ignore_list.watch('late', update);
      elm.appendChild(tbl);
    },
  };
})();
// Where are we?
// In the interest of sanity, ftl.thread is true for all thread-like pages
// (i.e. any page with one or more posts); ftl.threadlist is true for all
// threadlist-like pages (i.e. any page with a list of threads).  The
// formatting for all such pages is similar enough to treat them equivalently.
var ftl = {
  // Mobile pages
  mobile: '^https?://m\\.',
  // All forum pages
  forums: '^https?://(m|www)\\.fasttech\\.com/forums',
  // Thread-like pages (i.e. those with posts)
  thread: '^https?://(m|www)\\.fasttech\\.com/forums/[^/]+/t/[0-9]+/.',
  // Permalink pages
  perma: '^https?://(m|www)\\.fasttech\\.com/forums' +
      '/[^/]+/t/[0-9]+/[^/]+\\?[0-9]+$',
  // Threadlist-like pages (i.e. those with thread links)
  threadlist: '^https?://(m|www)\\.fasttech\\.com/forums' +
      '(/[^/]+(/page/[0-9]+)?(/search\\?.*)?)?$',
  // Lists of threads for a single author
  author: '^https?://(m|www)\\.fasttech\\.com/forums/author/',
  // Lists of threads for a single product
  product: '^https?://(m|www)\\.fasttech\\.com/p(roducts?)?/',
  // New thread page for a product
  pnthread: '^https?://(m|www)\\.fasttech\\.com/forums/[0-9]+/post',
  // Forum settings page
  settings: '^https?://www\\.fasttech\\.com/forums/settings$',
  // New arrivals pages
  new_arrivals: '^https?://(m|www)\\.fasttech\\.com/category/1/new-products',
  // Review pages
  reviews: '^https?://(m|www)\\.fasttech\\.com/reviews',
  // Product search
  prsearch: '^https?://(m|www)\\.fasttech\\.com/search',
  // Product category
  prcategory: '^https?://(m|www)\\.fasttech\\.com/c(ategory)?(/|$)',
};
for (var k in ftl)
  ftl[k] = new RegExp(ftl[k]).test(location.href);
// ftl.editor matches all pages with a post editor
ftl.editor = ftl.thread ||
    (ftl.threadlist && !ftl.settings && !ftl.author) ||
    ftl.pnthread;
// ftl.threadlist matches all pages listing threads
ftl.threadlist = ftl.threadlist || ftl.author || ftl.product || ftl.settings;
// ftl.forums matches all pages with forum elements (threads or posts)
ftl.forums = ftl.forums || ftl.product;
// ftl.reviews matches all pages with reviews
ftl.reviews = ftl.reviews || ftl.product;

if (ftl.thread || ftl.settings) {
  // Menu configuration
  var full_menus = [
    { title: 'Browsing', vars: ['use_theme', 'theme_name', 'track_viewed',
      'track_posts', 'always_unread', 'hide_threads'] },
    { title: 'Posting', vars: ['quote_quotes', 'quote_imgs'] },
    { title: 'Thread Menu', vars: ['inthread_menu', 'inthread_manage'] },
    { title: 'Post Hiding', vars: ['placeholders', 'hide_ignored',
      'hide_quotes', 'hide_deleted'] },
  ];
  var thread_menu;
  if (true) {
    thread_menu = [
      { title: 'General', vars: ['use_theme', 'quote_quotes', 'quote_imgs'] },
      { title: 'Post Hiding',
        vars: ['hide_ignored', 'hide_quotes', 'hide_deleted'] },
    ];
  } else {
    thread_menu = full_menus;
  }

  settings.register('inthread_menu', 'Show menu in thread toolbar', true, null);
  settings.register('inthread_manage', 'Manage ignored users from the menu',
      false, 'inthread_menu');
  settings.register('placeholders', 'Show placeholders for hidden posts', true,
      null);
  settings.register('hide_quotes', 'Hide posts quoting ignored users', false,
      'hide_ignored');
  settings.register('hide_deleted', 'Hide deleted posts', true, null);
  settings.register('quote_quotes', 'Include quotes in quotes', true, null);
  settings.register('quote_imgs', 'Include images in quotes', true, null);
}
if (ftl.threadlist) {
  settings.register('track_viewed', 'Link to last viewed page', false, null);
  settings.register('track_posts', 'Link to unread posts', true, null);
  settings.register('always_unread', 'Always show unread posts button', false,
      'track_posts');
  settings.register('hide_threads', 'Hide threads by ignored users', false,
      null);
}
if (ftl.editor) {
  settings.register('agree_tou', null, false, null);
}

block.block('ready');
if (ftl.forums) {
  var ignore_list = create_ignore_list('ignorelist');
  last_viewed.update();
  if (ftl.thread || ftl.reviews)
    user_tags.update();
  block.block('late');
  block.watch('ready', function() {
    setTimeout(function() { block.unblock('late'); }, 0);
  });
} else if (ftl.new_arrivals) {
  var ignore_categories = create_ignore_list('ignored_categories');
} else if (ftl.reviews) {
  user_tags.update();
}

settings.update();

if (document.readyState === 'loading') {
  document.addEventListener('readystatechange', function() {
    if (document.readyState === 'interactive') block.unblock('ready');
  });
} else {
  block.unblock('ready');
}

/* Fix up display of certain post elements:
 *  - Fix align tags (which should have been 100% divs to begin with)
 *  - Avoid breaking SKU links across lines
 */
var style = document.createElement('style');
style.innerHTML =
    '.PostContent span[style*="text-align:"] {display:block;width:100%}\n' +
    'a.SKUAutoLink {display: inline-block}\n' +
    '.PopoutPanel>ul {margin: -5px -20px 0px -50px!important}\n' +
    '.PopoutPanel>p {margin: 0px!important}\n' +
    '.Badges {cursor:pointer}\n' +
    '.itmanage tbody tr td {padding:0px}\n' +
    '.itmanage .itmremove {padding-left: 16px; height: 16px; background: ' +
    'url(https://www.fasttech.com/images/minus-button.png) no-repeat}';
document.head.appendChild(style);

// RE to pull a thread id from a URL
if (ftl.forums)
  var thread_id_re = new RegExp('/forums/[^/]+/t/([0-9]+)/[^?]*$');

if (ftl.thread) {
  // Add a filter for posts quoting ignored users
  post_filter.filter((function() {
    var quote_res = [];
    function new_qre(user) {
      quote_res.push(
          new RegExp('\\b' + user + ' wrote(</a>)?:?[\\r\\n]*<br>', 'i'));
    }

    function update(user, state, idx) {
      if (user instanceof Array) {
        quote_res = [];
        for (var i = 0; i < user.length; i++)
          new_qre(user[i]);
      } else {
        if (state)
          new_qre(user);
        else
          quote_res.splice(idx, 1);
      }
    }
    ignore_list.watch('early', update);
    settings.watch('hide_quotes', post_filter.update);

    return function(post) {
      if (settings.get('hide_ignored') && settings.get('hide_quotes')) {
        for (var i = 0; i < quote_res.length; i++) {
          if (quote_res[i].test(post.content_elm.innerHTML))
            return true;
        }
      }
    };
  })());

  // Add a filter for posts that the admins have deleted
  post_filter.filter((function() {
    var deleted_re = /post deleted/i;
    var edited_re = /^[ \r\n]*$/;
    settings.watch('hide_deleted', post_filter.update);

    return function(post) {
      if (settings.get('hide_deleted')) {
        var wb = post.content_elm.getElementsByClassName('WarningBox')[0];
        if ((wb !== undefined && deleted_re.test(wb.innerHTML)) ||
            edited_re.test(post.content_elm.innerHTML))
          return true;
      }
    };
  })());

  // Get all post bodies, desktop and mobile
  var posts;
  block.watch('ready', function() {
    posts = [];
    var elms = document.getElementsByClassName('PostContent');
    for (var i = 0; i < elms.length; i++) {
      var pc = elms[i];
      var user = pc.getAttribute('data-username');
      if (user !== null)
        posts.push({ user: user, content_elm: pc });
    }
  });

  // Create a function to create placeholders as needed
  var gen_watch_filter = function(ph_func) {
    return function(post, state) {
      var placeholders = settings.get('placeholders');
      if (state && placeholders) {
        if (!post.placeholder)
          post.placeholder = ph_func(post);
        post.placeholder.style.display = '';
      } else {
        if (post.placeholder !== undefined)
          post.placeholder.style.display = 'none';
      }
    }
  };

  // Get this thread's ID
  var thread_id = location.href.match(thread_id_re);
  if (thread_id !== null && thread_id[1] !== undefined)
    thread_id = parseInt(thread_id[1]);
  else
    thread_id = undefined;
}

// Get the current and last pages from a pager element, desktop and mobile
function parse_pager(elm) {
  var cur, last, sel;
  if (elm === undefined || elm === null) {
    cur = last = 1;
  } else if (!ftl.mobile) {
    sel = elm.getElementsByClassName('PageLink_Selected')[0];
    if (sel !== undefined)
      cur = parseInt(sel.innerHTML);
    last = parseInt(elm.lastElementChild.previousElementSibling.innerHTML);
  } else {
    sel = elm.getElementsByClassName('active')[0];
    if (sel !== undefined)
      cur = parseInt(sel.firstElementChild.innerHTML);
    last = parseInt(elm.lastElementChild.firstElementChild.innerHTML);
  }
  return {
    cur: cur,
    last: last
  };
}

if (!ftl.mobile) {
  /* Jump-to-page boxes are broken site-wide.  Fix them.
   * The existing JumpToPage box could be fixed by changing "function (e)" to
   * "function (event)" in the event listener, but instead, I'm doing all this.
   */
  var fix_jtp = function() {
    var jtps = document.getElementsByClassName('JumpToBox');
    if (jtps.length === 0)
      return;

    var lastpg = parse_pager(jtps[0].parentElement).last;
    function handle_kp(k) {
      if (k.which === 13) {
        k.preventDefault();
        k.stopPropagation();
        var pg = parseInt(this.value);
        if (pg > 0 && pg <= lastpg)
          location.href = base[1] + pg + base[2];
      }
      return false;
    }

    for (var i = 0; i < jtps.length; i++) {
      var jtpbox = jtps[i];
      var as = jtpbox.parentElement.getElementsByTagName('a');
      if (as[0] !== undefined) {
        var base = as[0].href.match(new RegExp('(.*/)[0-9]+($|[?#/].*)'));
        if (base !== null)
          jtpbox.addEventListener('keypress', handle_kp);
      }
    }
  };
  block.watch(ftl.forums ? 'late' : 'ready', fix_jtp);
}

if (ftl.threadlist) {
  // Translate a post number into a page number, handling FT's off-by-one quirk
  var postnr_to_pagenr = function(post, maxpage) {
    return Math.floor(Math.min(maxpage, post / 10 + 1));
  };

  // Create a link to a specific page of a thread
  var gen_page_link = function(base, page, hash) {
    var ret = base;
    if (page !== 1)
      ret += '/' + page;
    if (hash)
      ret += '#' + hash;
    return ret;
  };
}

if (!ftl.mobile) {
  // Re-implement FT's PopoutMenu, only nicer
  var PM2 = (function() {
    var active, timeout;

    function show() {
      var p = active.par;
      var c = active.ch;
      p.className += ' focused';
      c.style.left = c.style.top = '0px';
      c.style.display = 'inline';
      var ppb = p.parentElement.getBoundingClientRect();
      var pb = p.getBoundingClientRect();
      var cb = c.getBoundingClientRect();
      c.style.left = Math.min(pb.left - cb.left,
          document.body.clientWidth - cb.width) + 'px';
      c.style.top = (ppb.bottom - cb.top) + 'px';
    }

    function hide() {
      if (active === undefined)
        return;
      active.par.className = active.cname;
      active.ch.style.display = 'none';
      active = undefined;
    }

    function min(pm) {
      clearTimeout(timeout);
      if (pm !== active)
        hide();
      active = pm;
      show();
    }

    function mout() { timeout = setTimeout(hide, 300); }

    function add_listeners(elem, pm) {
      elem.addEventListener('mouseover', min.bind(null, pm));
      elem.addEventListener('mouseout', mout);
    }

    /* Hack: we can't remove anonymous event listeners, so duplicate elements.
     * We hide the existing element to serve as a decoy for the original
     * PopoutMenu in case it hasn't run yet, then steal all its children.
     */
    function dupe(id) {
      var elm = document.getElementById(id);
      var nelm = elm.cloneNode(false);
      elm.style.display = 'none';
      while (elm.childNodes[0])
        nelm.appendChild(elm.childNodes[0]);
      if (elm.nextElementSibling)
        elm.parentElement.insertBefore(nelm, elm.nextElementSibling);
      else
        elm.parentElement.appendChild(nelm);
      return nelm;
    }

    var def = {dupe: true};
    return function(pm) {
      for (var k in def) {
        if (!(k in pm))
          pm[k] = def[k];
      }
      try {
        if (pm.dupe) {
          pm.ch = dupe(pm.ch || (pm.par.replace('Button', '') + 'Popout'));
          pm.par = dupe(pm.par);
        } else {
          pm.ch = document.getElementById(
              pm.ch || (pm.par.replace('Button', '') + 'Popout'));
          pm.par = document.getElementById(pm.par);
        }
        pm.cname = pm.par.className;
        pm.ch.style.padding = '20px';
        add_listeners(pm.par, pm);
        add_listeners(pm.ch, pm);
      } catch (e) {}
    };
  })();

  block.watch('ready', function() {
    ['Cart', 'Support', 'Community', 'Account']
        .forEach(function(n) { PM2({par: n + 'Button'}); });
    if (ftl.thread) {
      ['RateThread', 'ForumTools']
          .forEach(function(n) { PM2({par: n}); });
    }
  });
}
styles.register('nightmode', undefined,
    '#SearchKeywords,#images ol,#searchBarCategory,button.close{background:0 ' +
    '0!important}#images ol{border:0!important}.btn.active,li.active>a{backgr' +
    'ound:#0d7bad!important}.FormButton.green{background:#20944b!important}#P' +
    'roductDetails,#RightPanel,#categoryButton,#container .list-group li a,#f' +
    'ooter,#searchBarOptionsCell,#searchBoxCell,#searchButtonCell,.AFEntry,.A' +
    'ttachments,.BGShadow,.BGShadowLight,.OrdersShipmentHeading,.PageNavigati' +
    'onalPanel,.ProductBackdrop,.ProductFilters,.ProductsGrid,.TicketResponse' +
    ',.TicketResponseFrame,.alert-info,.jumbotron,.list-group li,.mbbc-popups' +
    ' li,.mbbc-popups ul,.ui-dialog,body,td.Pros,ul.nav a{background:#222!imp' +
    'ortant}.FormButton.blue{background:#3182b9!important}#AccountButton.focu' +
    'sed,#LeftPanel,#UpdateWarningMsg,#container li a,#container li.active a.' +
    'dropdown-toggle,#container ol,#navbar>tbody>tr:first-child,.CardShadow,.' +
    'ControlArrows,.FilterEntry:hover,.FormButton.white,.ForumHeader,.ForumLi' +
    'nk:hover,.GridItem,.PopoutPanel,.ProductGridItem,.ProductPanelBackdrop,.' +
    'ReviewContent,.Steps>div,.btn,.dropdown-menu,.dropit-submenu>li,.mbbc-po' +
    'pups li:hover,.navbarsearch,.panel-body,.panel-heading,.panel-title,.tab' +
    'le,.table td,.tabs-min,button,div.well,input:not([valbbcode]),select,tex' +
    'tarea,ul.dropdown-menu>li>a{background:#333!important}.FilterSelected,.F' +
    'orumLink.Selected,.PageLink:hover,.StaticPageLink_Selected,.divider,.dro' +
    'p-hover>a,.dropit-submenu,.subCategories li:hover,hr{background:#444!imp' +
    'ortant}.FormButton.purple{background:#6a3b7f!important}.FormButton.red{b' +
    'ackground:#9e1531!important}.FormButton.orange{background:#c4571d!import' +
    'ant}.topLevelCategories li.focused{background-image:url(data:image/png;b' +
    'ase64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAZklEQVR4AWPwySwkG1' +
    'NPs2TjRjuyNUs0brwl0bRxj3jzei2yNEs2bfwPwkBD+sTb1wuQrhlhwCvJ5o25ZGhGYJA4KD' +
    'zI0YwwpGF9LMmaQYEIsplkP0s0b4wnO7TpE88gTXRN22RjAIeVNYXGl+mUAAAAAElFTkSuQm' +
    'CC)!important;background-position:right 2px!important;background-repeat:' +
    'no-repeat!important}.mbbc-buttons li{background-image:url(data:image/png' +
    ';base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAACVCAMAAACKJe8uAAAAjVBMVEVMaXG0UlJg' +
    'YmS+kI/MzMzMzMzMzMzMzMzAxLuoqKiamppWV1cuLi7MzMzMzMzAx8qNjo5FRUaoRkZ8fH1N' +
    'Tk/MzMzMzMyUPz55OjfMzMzMzMzMzMzMzMzMzMxWeJzJbgbGpxKCoqY8YIC5uqhzqsmwlV99' +
    'pIB1lTxlghlvfMlxYbKnRy1sk7skfGsNbDRy3rXhAAAAHnRSTlMA/////42hdf//////s8L/' +
    '///////W6///NiMRW0Sx+uQWAAAC5klEQVR4AezSRZLAMAxEUY3SQTkM8v0vOhxG7fNdXr9A' +
    '09vbqo9VZIuZTVaARcGshVEUhfGNlaSZWDDAwWFsxgggylFcWSWzVGLD0lOMgKv3YqLagjVA' +
    '61rnHH4OGguWsnxjnQn7ZY6xEP0FxmXdjZYFcz93i/XDzS+rK5NFzXqNTz8jM39bTkwWabNK' +
    'N1gBnGz+2yJr2gD4YXCI5cChxVzVzo6NzDE2IDq0SMbNm7HfgwBYYBHQR31xsnkiqToz5hvF' +
    'yCkaTw/ijEVau0VeFQr8Xqg+wkiqcfNGzOsi/wyjrvtip761hllBKAzvArGSTtcS8P5v8/x+' +
    'OceRk3iaPd07+QetXXsGKaX0bzQLl4kolRRhWBBnVIRRmghTB+III4yRI4yTIUzriKKV+kKk' +
    'lFJKiZ7ABZyZF6CFi+JSzlyX45lCjm3Y+1eGH6jNsMaLmJNiE1yr745/JXbBM+tigAVdWaWx' +
    'Sz+4MvyADxJFyJWtPxpRnW/G+MNnhu/ixsyNRsFTk/whdqVdQSU8Jw27tS5+Zl13seE561IL' +
    'Oy4mUpzF8JIuu/7faLCl+BvJI0dKKaUvMbe7xXHOhm3tPax2u13BcZXmw56m1LAZMY5jcmyF' +
    'Fr7F1jOGLxikD/sti55Z+AIivVuLiPVeFHa7h28jbXy3DWcJbQ971twNuduGw6aMog+bUkop' +
    'fYPI7U43Xcdj2LTVWbqExHQ4UKmExDoDKDQjYnUYgDYQERsMwAZHxBY5ACaPiFWagHeyiJhT' +
    'sVpF1HHYEKDTcPRR8V8jzw4OG3IPKaX/vb/aqQcchmIAAMPzmjzGC2rz/rebbfvvAb7637eX' +
    'rdfYCq13DwxjnBFCMkrpLtYBoJrVYQ/HOCFUTKuegGEphNL0XtjRMwO1ENQYfS/MWps55zLv' +
    '/S5WjTdQG2PUV2JCmSedmZo0oEqBu2AhhCzGmKWUdjFu9CzDn4EtegIGxj/IeIzrPP7MqlVj' +
    '7LEhtuoWbAQUelFEFIeMmgAAAABJRU5ErkJggg==)!important;border-radius:5px!im' +
    'portant}.HourGlass{background:inherit!important}.Stars{background:url(da' +
    'ta:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAXVBM' +
    'VEVMaXH/rVz/s2f/jCv/uGz/vXD+iSf+jSz+kjD+xH3+iiX+jCr+s2P/uG/+wXf+iif+m0T+' +
    'n0f+pE7+p1L+v3X+wHj+wn3/kjP/mD3/oEj/p1P/tGX/vnT/iCX/xX3uTskzAAAAF3RSTlMA' +
    '/grK/i3p1yHXJX7WA3aZbPXD7o2sP+NiEhsAAABkSURBVHgBZcvFAcMwEEXBL2aFzXb/ZUYY' +
    'fLdZQI0GfHaPnxo2Lt6iZls9bRDRbHwl17NGgjmOrGWeRmWBx1aVeEJ69GtSXrLyLkjdSY3S' +
    'rWhUqF3mKWmXjQmM7Z1BOQ1Yx/DXE5S8Bqiv9nzRAAAAAElFTkSuQmCC)!important}.Gra' +
    'yStars{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4A' +
    'AAAOCAYAAAAfSC3RAAAA4UlEQVR4AWLwySzEinfu3MkGwrjkcWo8efJk76lTp8pI0njmzBn7' +
    'Y8eO/T948OB/IG2AXyPCJl5AZ3RthjAARAF4F0omZBWquKfBrcPdOmyEMELk8XFxL+L5T0+n' +
    '04fgbDa7p5UcBk2Utt1u74fDwSE4nU6dwWCwkGW5JYpiw4coBRnwM444wtHtdh3TNB1FURye' +
    '558I8NNeJpNAGgpDjuPaMH5fKBEgDwmCsKd+Iz0SSEEo0UKJqVMdjUZfAmFEvWWuIwzCiGXZ' +
    'fEiA+sEB9IdWKkTTiBrfF+7xDkHisNbhAqOEltIAVAofAAAAAElFTkSuQmCC)!important}' +
    '.FilterEntry.FilterCancellable{background:url(data:image/png;base64,iVBO' +
    'Rw0KGgoAAAANSUhEUgAAAAwAAAALCAYAAABLcGxfAAAASklEQVR4AWPwySxkuCUk9J8QBqkD' +
    'YbBiYjGKBigAs3GJMTAwNDBgUYDOxtSAoQmhmCwNlDmJTE8TxggNiIgjrBimAYxBHEIYpA4A' +
    'F8kQ7Ga9snUAAAAASUVORK5CYII=) 10px 3px no-repeat,#444!important;border-c' +
    'olor:#0d7bad!important;box-shadow:0 2px 2px #111!important}input[src="/i' +
    'mages/errorreport.png"]{background:url(data:image/png;base64,iVBORw0KGgo' +
    'AAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEX////////////////////////////' +
    '///////////////////////////////////9Or7hAAAAAEHRSTlMBCBIcJS45RlVkcYOVqbz' +
    'NHir32QAAAGNJREFUeAEUTAkBBEEIUhMIDeAazPbvdjgvn0xVAXkCGocCwKcNaH4/9Sn08ym' +
    'Qae00DUBRAtbGIHJUDc1Y1DRuKRY25Y3/gOa8BwKQFe/ugcAHpnfv7p49e/bcPab/Z8HgPgC' +
    'LAx2nigq2jAAAAABJRU5ErkJggg==)!important}.CellMode .GridItemPrice{backgr' +
    'ound:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHUAAAAeCAYAAAASN' +
    '1ElAAAAi0lEQVR4Ae3aSRXDIABAwZiIjgrAQkV0X46VUT1RFAOV0M3Gf3MYBR8IEKbXfvMhZ' +
    'RG1Z4jasm7vj6kVlUswqlnaisqlFZX1Z25F5fmP2YrKHIxqlraiMovasvwjtqIyglHN0lZUR' +
    'iAqolp+sVHqcKTB5QOuCXGhj19vfpLjOQsenuGJKB5zM0TtWb7rxpkaVlv9JQAAAABJRU5Er' +
    'kJggg==) no-repeat!important;width:134px!important}.ForumHeader input[ty' +
    'pe=image]{border:0!important;vertical-align:text-top!important}#AddedToC' +
    'artPopupPanel,#container ol,.CardShadow,.ForumThread,.GridItem,.ProductG' +
    'ridItem,.ProductPanelBackdrop,.TicketResponseFrame,.dropit-submenu,.mbbc' +
    '-buttons li,.panel-body,.panel-heading,td.Pros{border:1px solid #444!imp' +
    'ortant}#searchBarOptionsCell,#searchBoxCell,#searchButtonCell,.ForumThre' +
    'ad tr td,.navbarsearch{border-bottom:1px solid #444!important}#Ignorelis' +
    'tPopout tr td,.PostFlags ul li{border-bottom:none!important}.FilterEntry' +
    ':hover,.FilterSelected,.StaticPageLink_Selected{border-color:#0d7bad!imp' +
    'ortant}#categoryButton,#container li a,#container ul,.ControlArrows,.For' +
    'umQuote,.ForumsNavigation,.PageLink,.PageNavigationalPanel,.PostFlags,.S' +
    'KUAutoLink,.Signature,.TightInformationBox,.list-group li,.mbbc-popups u' +
    'l,body .FormButton[class],div,hr,td{border-color:#444!important}.btn,but' +
    'ton,input:not([valbbcode]),select,textarea{border-color:#444!important;b' +
    'order-style:solid!important}#searchBarOptionsCell{border-left:1px solid ' +
    '#444!important}.ForumQuote{border-left:5px solid #0d7bad!important}.drop' +
    'it-submenu>li,blockquote{border:none!important}#searchButtonCell{border-' +
    'right:1px solid #444!important}.ForumLink.Selected,.ForumLink:hover{bord' +
    'er-right:3px solid #0d7bad!important}#searchBarOptionsCell,#searchBoxCel' +
    'l,#searchButtonCell{border-top:1px solid #444!important}#LeftPanel,.Popo' +
    'utPanel{box-shadow:0 15px 30px #111!important}.ProductGridItem{box-shado' +
    'w:0 3px 3px #111!important}.BGShadow,.BGShadowLight,.CardShadow,.FilterS' +
    'elected,.ForumLink.Selected,.GridItem,.StaticPageLink_Selected,.subCateg' +
    'ories li:hover{box-shadow:none!important}.SysMsgBanner{color:#000!import' +
    'ant}#AccountButton a{color:#0d7bad!important}#StaticPageContent [style],' +
    '#categoryButton a,#content_ProductName,#footer,.AFTitle,.ControlArrows a' +
    ',.FieldLabel,.FlagValue,.Gray,.GrayText,.GridItemDesc>div,.GridItemModel' +
    's,.Name>a,.NewList,.PageLink,.Pager,.btn,.dropit a,.navmenubutton a,.pan' +
    'el-default>.panel-heading,.panel-title,.subCategories a,.topLevelCategor' +
    'ies a,a.SortOrder,body,button,input:not([valbbcode]),select,textarea,ul.' +
    'dropdown-menu>li>a{color:#bbb!important}.mbbc-popups li{color:#ccc!impor' +
    'tant}.btn.active,body .FormButton[class],ul.dropdown-menu>li>a:hover{col' +
    'or:#fff!important}.GridItemPackSize{display:inline!important;float:none!' +
    'important;margin:0 0 0 -22px!important;vertical-align:sub!important}#DMC' +
    'A,.logo,.navseals,a[href$="/faq/money-back-guarantee"]>img{display:none!' +
    'important}#categoryButton{margin:0 20px!important}#footer,.PageContentPa' +
    'nel>div:last-child[style]{margin:0!important}#CartPopupClose{margin:-10p' +
    'x -20px!important}.mbbc-row1{margin-bottom:1px!important}.ForumLink:hove' +
    'r{margin-right:-3px!important}#Tab{margin-top:10px!important}#AddedToCar' +
    'tPopupPanel{padding:0 10px!important;position:fixed!important;right:40px' +
    '!important;top:40px!important}.menubuttons{padding:0 20px!important}#Ans' +
    'wer{padding:0!important}#searchbar{padding-right:20px!important}#AddedTo' +
    'CartPopupPanel,#categoryButton{width:auto!important}');

styles.register('darkft', undefined,
    '.FieldFlag,.Pager .PageLink{font-size:9pt!important}.Pager .PageLink:hov' +
    'er{background:#111!important;color:#fefefe!important}.FieldFlag{backgrou' +
    'nd:#1980b0!important;font-weight:400!important;padding:3px 5px!important' +
    '}.ForumLink.Selected{background:#222!important;border-right:3px solid #6' +
    '66!important}.ForumHeader,.ForumThread .ThreadCommandBar,.logo,body,div#' +
    'contents{background:#333!important}.logo{visibility:hidden!important}.Fo' +
    'rumThread tr td{background:#363636!important}#AccountButton.focused,#Acc' +
    'ountPopout,#AddedToCartPopupPanel,#CartPopout,#CartPopupPanel,#Community' +
    'Popout,#LeftPanel,#SupportPopout,#categoryButton,#categoryPopout,#navbar' +
    ',#searchBarOptionsButton,.CellMode .ProductGridItem,.FilterEntry:hover,.' +
    'FormButton.white,.FormButton.white:hover,.LargeFilterEntry:hover,.LineMo' +
    'de .ProductGridItem,.ModalDialog,.OrdersShipmentHeading,.PageNavigationP' +
    'anel,.ProductBackdrop,.ProductFilters,.ProductFilters .FilterCancellable' +
    ',.ProductFilters .FilterSelected,.ProductPanelBackdrop,.mbbc .mbbc-edito' +
    'r,div#footer,hr,tr{background:#444!important}.HourGlass{background:#444f' +
    'e5!important}.Cons .FieldFlag,.Pros .FieldFlag{background:#4c9ed9!import' +
    'ant}#navbar .focused,.BGShadow,.BGShadowLight,.PopoutPanel,.Steps .focus' +
    'ed{background:#666!important}.PopoutPanel{border-radius:0!important;box-' +
    'shadow:0 5px 5px #888!important}#searchBarCategory{background:#777!impor' +
    'tant}.Pager .ControlArrows{background:#888!important;font-weight:400!imp' +
    'ortant}.Pager .PageLink_Selected{background:#999!important}#ForumPopout,' +
    '#ForumToolsPopout,#RateThreadPopout{background:#fff!important}#CartButto' +
    'n,#CommunityButton,#SupportButton,#searchBarOptionsCell,#searchButtonCel' +
    'l,.GridViewIcon,.GroupViewIcon,.ProductGridMenu,.searchbar_focus #search' +
    'BarOptionsCell,.searchbar_focus #searchButtonCell{background-image:url(d' +
    'ata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAJYBAMAAADh0yLyAAAAJ1B' +
    'MVEVMaXFqbG11dXX9/f3l5eXd3eBadIdWbnlWc4RYieRmZ2dpaWlsbW2OPQlLAAAADXRSTlM' +
    'A////////////r0YgYtO1qQAAAq9JREFUeAHt2sd160YYx9FxKgBcekc24ADsHTizVwBakEr' +
    'g6+CpgBdLcHYJLsA9KTmnZ0PQof+U7j0q4Hc+fMM0KvcAALpI9xsocL2K+/t94GoT6KACNyZ' +
    '4oIF20AQF2kGBdtAEBdpBgXbQBAXaQYF20AQF2kGBdtAEI5UMAAAA4V9oBC43938PV/sO3My' +
    '1FihQoECB+yBQoECBAgUKFChQoECBAgUKFChQoEA3TcsBAAAA9CXbUEuq+pOhpNoO/fVfTZ5' +
    'ga3Wqn6Y/4hLr3ToM4YFj9gRLHdIDb5VwyTuYPsFh4SExwXeGeh35YQEAAACAf/h1K/8aogq' +
    '8q7rt+74lB/bXhvjAfkwPrAIXHZKaHNj6oUYHXsf1/ZAcONRtn/5O0o/ZgW3rvTjx4xYAAAA' +
    'ArsIEtjrWlj3BbfojHrbxOziEBw7pE0x/xKfN6+ACday1tpY8wTF7gq2168ZxfHw76CoMAAA' +
    'AAM4ild+c7xL9PrBLdEiBT1ebQA8hUKBAgQIFChQoUKBAgQIFChQoUKBAgQIFChSYftN0cRE' +
    'ZmAEAAOBstrJfu7melP3q5hL4J5u54gN3AgUKFChQoECBAgUKFChQoECBAgUKFChQoMCMwN1' +
    'cjzgQAABgmt6bjqZJoEA8YoF4xHsgMA0AADD9ROBs2EHsoEDs4OEDAADaNH1akm1bbSXZONa' +
    'jo/CvnUfvlUUcklpryz4ktT2PPiRTe/08+pCMS3/68E7yXvg7yadH4e8kU0l/Jyl3BAAAfPm' +
    'T70uqzU/eTw9c/5Ae+P0jDRQoUKDArutWJrgwcP3QJ+iQdE5x+ATtoAl6oRYoUOAeCBRYUnU' +
    '/+aqk+vEnBQAAF9qRXGgLFChQoMBlXt9YCbSD/5+zGw7Jwgme28EFuht20Au1QIECBQp0oe1' +
    'COx0AAFf2ljch8gqLTAAAAABJRU5ErkJggg==)!important}#navbar tr.navbarsearch' +
    ',.navsealsbg,body.stripe{background-image:url(data:image/png;base64,iVBO' +
    'Rw0KGgoAAAANSUhEUgAAAGQAAABGAQMAAAAASKMqAAAAA1BMVE2.5EQ1TRdOAAAAEElEQVR4' +
    'AWMYVmAUjIJRAAAD1AABVz/btAAAAABJRU5ErkJggg==)!important}.searchbar_focus' +
    ' #searchBoxCell{background-image:url(data:image/png;base64,iVBORw0KGgoAA' +
    'AANSUhEUgAAAD0AAAAtBAMAAADvmn5BAAAAD1BMVEWYmJmzs7PBwcHGxsagoaK4fiRWAAAAI' +
    'ElEQVR4AWNiwA+YBPECgcGuf6DkR+VH5UflR+UpLd8AJUsIkz10gdcAAAAASUVORK5CYII=)' +
    '!important}#searchBoxCell{background-image:url(data:image/png;base64,iVB' +
    'ORw0KGgoAAAANSUhEUgAAAD0AAAAtAQMAAAAnevExAAAAA1BMVEV3d3dY20ihAAAADUlEQVR' +
    '4AWMYfGAUAAABlQABpWSZFgAAAABJRU5ErkJggg==)!important}.SysMsgBanner{borde' +
    'r:1px dotted #444!important}#DMCA div.dm-1,#DMCA div.dm-2,#LeftPanel,#ca' +
    'tegoryButton,#searchBarOptionsButton,.BorderedPanel,.FormButton,.ForumTh' +
    'read,.ForumThread .Signature,.InformationBox,.Pager .ControlArrows,.Page' +
    'r .PageLink,.Pager .PageLink_Selected,.ProductThumbnails,.Reviews .Cons,' +
    '.SKUAutoLink,.SingleLineTextBox,.TightInformationBox,.VerticalTextArea,.' +
    'WarningBox{border:1px solid #444!important}.ForumQuote{border:1px solid ' +
    '#555!important;border-left:5px solid #666!important}.mbbc .mbbc-editor{b' +
    'order:none!important}td{border-bottom:0!important}#ProductDetails .Attri' +
    'butesTableRowGroupTitle{border-bottom:1px dashed #444!important}.Discoun' +
    'tsTable,.ProductDescriptions,.fixedCategoryLink,.scHeading{border-bottom' +
    ':1px dotted #444!important}#navbar,.FilterHeading,.FooterBanner,.PostFla' +
    'gs ul li,.tabs-min .ui-widget-header{border-bottom:1px solid #444!import' +
    'ant}.SingleLineTextBox:focus,.VerticalTextArea:focus{border-color:#444!i' +
    'mportant}.ForumThread .Signature,.Reviews .ReviewContent{border-left:5px' +
    ' solid #444!important}.ForumsNavigation{border-right:0 solid #e0e0e0!imp' +
    'ortant}#ProductSpecifications,.FooterLeft,.navmenubutton_separator{borde' +
    'r-right:1px dotted #444!important}.PostFlags,td.PageNavigationalPanel{bo' +
    'rder-right:1px solid #444!important}.FilterEntry:hover,.LargeFilterEntry' +
    ':hover,.ProductFilters .FilterCancellable,.ProductFilters .FilterSelecte' +
    'd,.subCategories li:hover,div.StaticPageLink_Selected{border-right:3px s' +
    'olid #444!important}#navbar .hdivider,.FooterBanner,.OrdersShipmentHeadi' +
    'ng,.PopoutPanel{border-top:1px solid #444!important}.BGShadow{box-shadow' +
    ':0 1px 1px #666!important}.BGShadowLight{box-shadow:0 3px 3px #666!impor' +
    'tant}#CartPopout .ViewCart a:hover,#DMCA,#DMCA div.dm-1 a,#DMCA div.dm-2' +
    ' a,#ProductDetailsPanel .Price,#navbar .focused a,#searchButtonCell,.Cel' +
    'lMode .GridItemPrice,.FieldFlag a,.FormButton,.FormButton.blue:hover,.Fo' +
    'rmButton.green:hover,.FormButton.orange:hover,.FormButton.purple:hover,.' +
    'FormButton.red:hover,.LineMode .GridItemPrice,.MemberRankIndex,.Pager .P' +
    'ageLink_Selected,.Pager .PageLink_Selected:hover,.Pager .PageLink_Select' +
    'ed:visited,.Steps .focused,.Steps .focused .StepText,.Steps .focused .St' +
    'epTitle,.tabs-min .ui-state-default a,.unitSelector{color:#444!important' +
    '}.tabs-min .ui-state-active a{color:#666!important}#navbar .menulinks sp' +
    'an a:active,a,a:visited{color:#d5d5d5!important}#categoryButton,#searchB' +
    'arCategory,.CartTable .Name a,.CellMode .GridItemModels,.FormButton.whit' +
    'e,.FormButton.white:hover,.ForumThread .ThreadCommand,.Information,.Line' +
    'Mode .GridItemModels,.Pager .PageLink,.navmenubutton a,.subCategories a,' +
    '.topLevelCategories a,.topLevelCategories li.focused a{color:#e1e1e1!imp' +
    'ortant}.Black{color:#e2e2e2!important}.PageTitle{color:#e5e5e5!important' +
    '}#categoryButton a,.PostFlags .FlagValue,.SingleLineTextBox:focus,.Verti' +
    'calTextArea:focus{color:#f1f1f1!important}body{color:#f3f3f3!important}#' +
    'content_ProductName,.mbbc .mbbc-align-popup li,.mbbc .mbbc-code-popup li' +
    ',.mbbc .mbbc-editor,.mbbc .mbbc-font-popup li,.mbbc .mbbc-list-popup li,' +
    '.mbbc .mbbc-simple-popup li,.mbbc .mbbc-size-popup li,.subCategories a:h' +
    'over,.topLevelCategories a:hover,a:hover{color:#fff!important}.MemberRan' +
    'kUsername{font-weight:700!important}.ForumGroup{padding:3px 10px!importa' +
    'nt}.mbbc .mbbc-buttons ul{padding:5px 0 3px!important}.mbbc .mbbc-emotic' +
    'on-popup .mbbc-group li{padding:8px!important}');

settings.register('use_theme', 'Use custom theme', false, null);
settings.register('theme_name', null, 'nightmode', 'use_theme', {
  nightmode: { name: 'Night mode' },
  darkft: { name: 'Pafapaf\'s DarkFT' },
});
styles.update();
// Add a "Top of page" button at the bottom of the page, aligned with the pager
if ((ftl.thread && !ftl.perma) || (ftl.threadlist && !ftl.settings)) {
  var add_top_btn;
  if (!ftl.mobile) {
    add_top_btn = function() {
      var elm = document.getElementsByClassName('ForumThread TightTable')[0];
      if (elm)
        elm = elm.nextElementSibling;
      if (!elm)
        return;
      elm.className = 'Pager';
      var tbtn = document.createElement('span');
      tbtn.className = 'ControlArrows';
      tbtn.setAttribute('style', 'float: left; min-width: 150px; ' +
          'max-width: 250px; text-align: center');
      tbtn.innerHTML = '<a>Top of page</a>';
      tbtn.firstElementChild.href = 'javascript:void(0)';
      handlers.top_click(tbtn);
      var pager = elm.firstElementChild;
      elm.appendChild(tbtn);
      if (pager) {
        var pspan = document.createElement('span');
        pspan.appendChild(pager);
        elm.appendChild(pspan);
      } else {
        elm.parentElement
            .insertBefore(document.createElement('br'), elm.nextElementSibling);
      }
    };
  } else {
    add_top_btn = function() {
      var elm = document.getElementsByClassName('ListRow')[0];
      if (elm)
        elm = elm.parentElement.nextElementSibling;
      if (!elm)
        return;
      var tbtn = document.createElement('button');
      tbtn.className = 'btn btn-default';
      tbtn.innerHTML = 'Top';
      handlers.top_click(tbtn);
      if (elm.firstElementChild) {
        tbtn.style.marginTop = '-5px';
        elm = elm.firstElementChild;
        var td = document.createElement('td');
        td.style.textAlign = 'right';
        td.appendChild(tbtn);
        elm.style.width = '100%';
        elm.firstElementChild.firstElementChild.appendChild(td);
      } else {
        var d = elm.appendChild(document.createElement('div'));
        d.setAttribute('style', 'width: 100%; text-align: right; ' +
            'margin-bottom: 8px');
        d.appendChild(tbtn);
      }
    };
  }
  block.watch('ready', add_top_btn);
}

if (ftl.editor) {
  var bbeeditor; // element to scroll to
  var bbetext; // textarea
  var bbeexpand; // expand button (mobile)
  var bbepanel; // parent of editor
  if (!ftl.mobile) {
    var fix_editor = function() {
      if (bbetext === undefined)
        bbetext = document.getElementsByClassName('mbbc-editor')[0];
      if (bbetext !== undefined) {
        // Save & restore the editor text
        if (history.state && history.state.saved_post)
          bbetext.value = history.state.saved_post;
        window.addEventListener('pagehide', function() {
          var state = history.state;
          if (state === null)
            state = {};
          state.saved_post = bbetext.value;
          history.replaceState(state, '');
        });

        // Remove the broken size=[67] buttons
        var size_popup = document
            .getElementsByClassName('mbbc-size-popup')[0];
        if (size_popup !== undefined) {
          size_popup.lastElementChild.remove();
          size_popup.lastElementChild.remove();
        }
        // Fix the editor width
        bbetext.style.boxSizing = 'border-box';
        bbetext.style.width = '100%';
        // Add a YouTube button
        var url_btn = document.getElementsByClassName('mbbc-url')[0];
        if (url_btn !== undefined) {
          var yt_btn = document.createElement('li');
          yt_btn.innerHTML = '&nbsp;';
          yt_btn.className = 'mbbc-youtube';
          yt_btn.addEventListener('click', function() {
            var sp = bbetext.selectionStart;
            var ep = bbetext.selectionEnd;
            var sel;
            if (ep < sp) {
              sp = ep;
              ep = bbetext.selectionStart;
            }
            if (ep !== sp) {
              sel = bbetext.value.slice(sp, ep);
            } else {
              sel = prompt('Video URL:', '');
              if (sel === null)
                return;
              var vid = sel
                  .match(new RegExp('[/?=#]([-\\w]{11})([^-\\w]|$)'));
              if (vid !== null)
                sel = 'https://www.youtube.com/watch?v=' + vid[1];
            }
            if (sel === undefined)
              return;
            bbetext.value = bbetext.value.slice(0, sp) +
                '[youtube]' + sel + '[/youtube]' +
                bbetext.value.slice(ep);
            bbetext.selectionStart = bbetext.selectionEnd = sp + 9 +
                (sel === '' ? 0 : 10 + sel.length);
            bbetext.focus();
          });
          url_btn.parentElement.insertBefore(yt_btn, url_btn);
        }
      }
    };

    block.watch('late', function() {
      // Persist the terms of use checkbox
      var toubox = document.getElementById('AgreeTerms');
      if (toubox !== null)
        handlers.checkbox(toubox, 'agree_tou');

      bbeeditor = document.getElementById('bbEditor');
      if (bbeeditor !== null) {
        bbetext = bbeeditor.getElementsByClassName('mbbc-editor')[0];
        if (bbetext !== undefined)
          fix_editor();
        else
          new MutationObserver(function(es, mo) {
            mo.disconnect();
            fix_editor();
          }).observe(bbeeditor, { childList: true });
      }
    });
  } else {
    block.watch('ready', function() {
      bbetext = document.getElementById('RawContent');
      if (bbetext !== null) {
        bbepanel = bbetext.parentElement.parentElement.parentElement;
        bbeeditor = bbepanel.parentElement;
        bbeexpand = bbeeditor.previousElementSibling.firstElementChild
            .firstElementChild;
      }
    });
  }
}

if (ftl.thread && !ftl.perma) {
  // Re-implement quotes in a way that sort of works
  var fix_quote_btns = (function() {

    function wrap_tag(tag, val, str) {
      return '[' + tag + (val ? '=' + val : '') + ']' + str + '[/' + tag + ']';
    }

    function inner_quote(elm, range, depth) {
      var tmp = '';
      for (var e = elm.firstChild; e !== null; e = e.nextSibling) {
        var sp, ep;
        if (range !== undefined) {
          if (range.startContainer.compareDocumentPosition(e) === 2)
            continue;
          if (range.endContainer.compareDocumentPosition(e) === 4)
            break;
          if (range.startContainer === e)
            sp = range.startOffset;
          if (range.endContainer === e)
            ep = range.endOffset;
        }
        switch (e.tagName) {
          case 'BR':
            tmp += '\n';
            break;

          case 'STRONG':
            tmp += wrap_tag('b', '', inner_quote(e, range, depth));
            break;

          case 'EM':
            tmp += wrap_tag('i', '', inner_quote(e, range, depth));
            break;

          case 'HR':
            tmp += '[hr]';
            break;

          case 'P':
            if (e.innerHTML !== '')
              tmp += '\n' + inner_quote(e, range, depth);
            break;

          case 'SPAN':
            var y = e.style;
            var t = null;
            var v = '';
            if (y.textDecoration === 'underline') {
              t = 'u';
            } else if (y.textDecoration === 'line-through') {
              t = 'y';
            } else if (y.fontSize !== '') {
              t = 'size';
              v = parseInt(y.fontSize);
            } else if (y.color !== '') {
              t = 'color';
              v = y.color;
            } else if (y.textAlign !== '') {
              t = 'align';
              v = y.textAlign;
            } else {
              console.log('Unknown span!');
            }

            tmp += wrap_tag(t, v, inner_quote(e, range, depth));
            break;

          case 'DIV':
            if (e.className === 'ForumQuote') {
              if (tmp[tmp.length - 1] !== '\n')
                tmp += '\n';
              if (depth === 0 && settings.get('quote_quotes'))
                tmp += wrap_tag('quote', '',
                    inner_quote(e.firstElementChild, range, depth + 1).trim());
            } else {
              console.log('Unknown div!');
            }

            break;

          case 'IFRAME':
            var m = e.src.match(/youtube[^\/]*.com.*\/embed\/([^?]+)/);
            if (m !== null && m[1] !== undefined) {
              /* A YouTube tag inside a quote breaks the parsing of the quote.
              tmp += wrap_tag('youtube', '',
                'https://www.youtube.com/watch?v=' + m[1])
              */
              tmp += wrap_tag('url',
                  'https://www.youtube.com/watch?v=' + m[1], 'YouTube video');
            } else {
              console.warn('Unknown iframe!');
            }
            break;

          case 'IMG':
            if (settings.get('quote_imgs')) {
              tmp += wrap_tag('img', '', e.src);
            } else if (e.parentElement.tagName !== 'A') {
              tmp += wrap_tag('url', e.src, 'Image');
            } else {
              tmp += 'Image';
            }
            break;

          case 'A':
            if (e.className === 'SKUAutoLink') {
              tmp += e.textContent;
            } else {
              var do_quote = true;
              if ((depth > 0 || !settings.get('quote_quotes') &&
                  e.innerHTML.match(/\bwrote$/) !== null)) {
                // Is this a permalink for a quote we'll remove?
                var s = e;
                var l;
                do {
                  if (s.nextSibling === null) {
                    l = (l === undefined) ? s : l;
                    s = s.parentElement.nextSibling;
                  } else {
                    s = s.nextSibling;
                  }

                  /* Between the <a> and the <div>, we only want to see a
                   * colon and an unpredictable number of <br> and empty <p>
                   * elements.  The <div> may be outside the current element.
                   */
                  if (s.tagName === 'DIV' && s.className === 'ForumQuote') {
                    do_quote = false;
                    e = (l === undefined) ? s : l;
                    break;
                  } else if (s.tagName === undefined) {
                    if (s.textContent.match(/[^:\r\n]/) !== null)
                      break;
                  } else if (s.innerHTML !== '' ||
                      (s.tagName !== 'P' && s.tagName !== 'BR')) {
                    break;
                  }
                } while (true);
              }
              if (do_quote)
                tmp += wrap_tag('url', e.href, inner_quote(e, range, depth));
            }
            break;

          case undefined:
            // Filter out newlines and empty tags
            tmp += e.wholeText.slice(sp ? sp : 0, ep)
                .replace(/[\r\n]+/mg, '')
                .replace(/\[([^\]=]*)(=[^\]]*)?\]\[\/\1\]/mg, '');
            break;

          default:
            console.warn('Unhandled tag ' + e.tagName);
        }
      }
      return tmp;
    }

    function quote(user, elm) {
      if (bbetext === null) {
        console.error('bbEditor text box not found!');
        return;
      }
      var post_id;
      var pid = elm.id.match(/^POST([0-9]+)$/);
      if (pid !== null)
        post_id = pid[1];

      var sel = getSelection();
      if (sel.rangeCount > 0) {
        sel = sel.getRangeAt(0);
        if (sel.collapsed || !sel.intersectsNode(elm))
          sel = undefined;
      } else {
        sel = undefined;
      }

      if (bbetext.value.length > 0 &&
          bbetext.value[bbetext.value.length - 1] !== '\n')
        bbetext.value += '\n';
      if (thread_id !== undefined && post_id !== undefined)
        bbetext.value += wrap_tag('url', '/forums/-/t/' + thread_id + '?' +
            post_id, user + ' wrote') + ':\n';
      else
        bbetext.value += user + ' wrote:\n';
      bbetext.value += wrap_tag('quote', '', inner_quote(elm, sel, 0)
          .trim().replace(/[\r\n]{3,}/mg, '\n\n')) + '\n';

      if (bbeeditor)
        bbeeditor.scrollIntoView();

      if (bbeexpand && bbepanel && bbepanel.className &&
          !/\bin\b/.test(bbepanel.className))
        bbeexpand.click();
    }

    var quote_click = handlers.generic({ onclick: function() {
      quote(this.data.getAttribute('data-username'), this.data);
    }});

    return function(elms, func) {
      for (var i = 0; i < elms.length; i++) {
        var elm = elms[i].content_elm;
        quote_click(func(elm), elm);
      }
    };
  })();

  if (!ftl.mobile) {
    var replace_quotebtn = function(elm) {
      // Ugh.
      var oldbtn = elm.parentElement.parentElement.parentElement
          .previousElementSibling.firstElementChild.lastElementChild
          .firstElementChild;
      var newbtn = document.createElement('a');
      newbtn.innerHTML = 'quote/reply';
      newbtn.href = 'javascript:void(0)';
      oldbtn.parentElement.insertBefore(newbtn, oldbtn);
      oldbtn.remove();
      return newbtn;
    };

    block.watch('late', function() {
      fix_quote_btns(posts, replace_quotebtn);
    });
  } else { // mobile
    var replace_quotebtn_m = function(elm) {
      // Ugh.
      var oldbtn = elm.parentElement.firstElementChild.lastElementChild
          .lastElementChild.firstElementChild;
      var newbtn = document.createElement('a');
      newbtn.innerHTML = oldbtn.innerHTML;
      newbtn.href = 'javascript:void(0)';
      newbtn.tabindex = '-1';
      newbtn.role = 'menuitem';
      oldbtn.parentElement.insertBefore(newbtn, oldbtn);
      oldbtn.remove();
      return newbtn;
    };

    block.watch('late', function() {
      fix_quote_btns(posts, replace_quotebtn_m);

      // Add a link to forum settings to allow ignorelist management
      // Ugh.  There's no semi-robust way to select this.
      var li = document.querySelector('ul.nav:nth-child(1)>li:nth-child(3)' +
          '>ul:nth-child(2)>li:nth-child(8)');
      if (li !== null) {
        var new_li = document.createElement('li');
        new_li.innerHTML = '<a title="Forum Settings" ' +
            'href="https://www.fasttech.com/forums/settings">' +
            'Forum Settings</a>';
        li.parentElement.insertBefore(new_li, li);
      }
    });
  }
}

if (ftl.thread && ftl.mobile) {
  // Fix up YT iframe size: CSS doesn't work well here.
  var fr_resize = (function() {
    var frs;
    var raf;
    function do_resize() {
      for (var i = 0; i < frs.length; i++) {
        var fr = frs[i];
        fr.elm.style.height = Math.ceil(fr.elm.clientWidth * fr.ar) + 'px';
      }

      raf = undefined;
    }
    return function(fr) {
      if (frs === undefined) {
        frs = [];
        window.addEventListener('resize', function() {
          if (raf === undefined)
            raf = requestAnimationFrame(do_resize);
        });
      }
      fr.style.maxWidth = fr.width + 'px';
      fr.style.width = '100%';
      frs.push({elm: fr, ar: fr.height / fr.width});
    };
  })();

  block.watch('ready', function() {
    var frs = document.getElementsByTagName('iframe');
    for (var i = 0; i < frs.length; i++)
      fr_resize(frs[i]);
  });
}
if (ftl.thread && !ftl.perma) {
  var scroll_to_hash = function() {
    function scroll(elm) {
      if (elm) {
        var sd = elm.style.display;
        elm.style.display = '';
        elm.scrollIntoView();
        elm.style.display = sd;
      }
    }
    if (location.hash === '#unread' || location.hash === '#last')
      last_viewed.get(thread_id, function(hash, lvp, lpn, lp) {
        block.watch('filter', function() {
          var i;
          var pid = 'POST' + lp;
          for (i = posts.length - 1; i >= 0; i--) {
            if (posts[i].content_elm.id === pid)
              break;
          }
          if (i < posts.length - 1)
            scroll(posts[i + 1].post_elms[0]);
          else if (hash === '#last')
            scroll(posts[posts.length - 1].post_elms[0]);
        });
      }, location.hash);
    else if (location.hash !== '')
      block.watch('filter', function() {
        scroll(document.getElementById(location.hash));
      });
    // Drop the hash so we won't run again
    history.replaceState(history.state, '', location.pathname);
  };
}

if (ftl.threadlist) {
  if (!ftl.mobile) {
    var add_last_viewed =
        function(elm, last_viewed_page, last_post_nr, last_post_id) {
      var row = elm.parentElement;
      var pelm = row.parentElement.getElementsByClassName('Pager')[0];
      var p = parse_pager(pelm);
      var skip_margin = false;

      if (pelm === undefined) {
        skip_margin = true;
        if (!ftl.settings) {
          pelm = document.createElement('div');
          pelm.className = 'Pager';
          row.appendChild(pelm);
        } else {
          pelm = row.parentElement.appendChild(document.createElement('div'));
          pelm.className = 'Pager';
        }
      }

      if (settings.get('track_viewed')) {
        var lv = document.createElement('a');
        lv.href = gen_page_link(elm.href, last_viewed_page);
        lv.innerHTML = 'Last viewed';
        lv.className = 'FormButton blue';
        if (!skip_margin)
          lv.style.marginLeft = '5px';
        pelm.appendChild(lv);
        skip_margin = false;
      }

      var tr = row.parentElement.parentElement;
      var ts = tr.getElementsByClassName('ThreadStats')[0];
      var pc;
      if (ts !== undefined)
        pc = (pc = ts.innerHTML.match(/Replies: ([0-9]+)/)) ?
            parseInt(pc[1]) : undefined;
      var unread = pc > 0 && pc >= last_post_nr;
      if ((unread || settings.get('always_unread')) &&
          settings.get('track_posts')) {
        var np = document.createElement('a');
        if (unread) {
          np.innerHTML = 'Unread posts';
          np.className = 'FormButton red';
          np.href = gen_page_link(elm.href,
              postnr_to_pagenr(last_post_nr, p.last), 'unread');
        } else {
          np.innerHTML = 'No unread posts';
          np.className = 'FormButton white';
          np.href = gen_page_link(elm.href,
              postnr_to_pagenr(last_post_nr, p.last), 'last');
        }
        if (!skip_margin)
          np.style.marginLeft = '5px';
        pelm.appendChild(np);
        skip_margin = false;
      }
    };

    var mangle_list = function() {
      var header = document.getElementsByClassName('ForumHeader')[0];
      var filter = !ftl.settings && !ftl.author && settings.get('hide_threads');
      var sku_re = new RegExp('fasttechcdn.com/[0-9]+/([0-9]+)/.*');
      var author_re = new RegExp('started\\W+by\\W+(\\w+)');
      if (header !== undefined) {
        var ch = header.parentElement.children;
        for (var i = 1; i < ch.length; i += 2) {
          var tr = ch[i];
          var tl = tr.getElementsByClassName('ThreadLink')[0];
          var tid;
          if (tl !== undefined &&
              (settings.get('track_posts') || settings.get('track_viewed'))) {
            tid = parseInt(tl.href.match(thread_id_re)[1]);
            if (!isNaN(tid))
              last_viewed.get(tid, add_last_viewed, tl);
          }
          var img = tr.firstElementChild.firstElementChild;
          if (img && img.tagName === 'IMG') {
            var sku = img.src.match(sku_re);
            if (sku !== null) {
              var p = img.parentElement;
              var a = document.createElement('a');
              a.href = '/products/' + sku[1];
              a.appendChild(img);
              p.appendChild(a);
            }
          }
          if (filter) {
            var u = tl.parentElement.textContent
                .match(author_re);
            if (u && u[1] && ignore_list.get(u[1])) {
              tr.style.display = 'none';
              if (tr.previousElementSibling !== header)
                tr.previousElementSibling.style.display = 'none';
            }
          }
        }
      }
    };

    block.watch('ready', mangle_list);
  } else { // mobile
    var add_last_viewed =
        function(elm, last_viewed_page, last_post_nr, last_post_id) {
      var rh = elm.parentElement;
      var tr = rh.parentElement;
      var p = parse_pager(tr.getElementsByClassName('pagination')[0]);
      var d = document.createElement('div');
      d.style.marginLeft = '-5px';

      if (settings.get('track_viewed')) {
        var lv = document.createElement('span');
        lv.innerHTML = '<a href="' +
            gen_page_link(elm.href, last_viewed_page) + '">Last viewed</a>';
        lv.className = 'btn btn-default';
        lv.style.margin = '5px';
        d.appendChild(lv);
      }

      var pc = (pc = tr.textContent.match(/R(eplies)?: ([0-9]+)/)) ?
          parseInt(pc[2]) : undefined;
      var unread = pc > 0 && pc >= last_post_nr;
      if ((unread || settings.get('always_unread')) &&
          settings.get('track_posts')) {
        var np = document.createElement('span');
        np.className = 'btn btn-default';
        np.style.margin = '5px';
        var npa = document.createElement('a');
        if (unread) {
          npa.innerHTML = 'Unread posts';
          npa.href = gen_page_link(elm.href,
              postnr_to_pagenr(last_post_nr, p.last), 'unread');
          npa.style.color = '#C64148';
        } else {
          npa.innerHTML = 'No unread posts';
          npa.style.color = '#888';
          npa.href = gen_page_link(elm.href,
              postnr_to_pagenr(last_post_nr, p.last), 'last');
        }
        np.appendChild(npa);
        d.appendChild(np);
      }

      rh.appendChild(d);
    };

    var mangle_list = function() {
      var filter = settings.get('hide_threads');
      var elms = document.getElementsByClassName('ListRow');
      var author_re = new RegExp('started\\W+by\\W+(\\w+)');
      for (var i = 0; i < elms.length; i++) {
        var elm = elms[i];
        var rh = elm.getElementsByClassName('RowHeading')[0];
        if (rh === undefined)
          continue;
        var l = rh.firstElementChild;
        if (l !== undefined &&
            (settings.get('track_posts') || settings.get('track_viewed'))) {
          var tid = parseInt(l.href.match(thread_id_re)[1]);
          if (!isNaN(tid))
            last_viewed.get(tid, add_last_viewed, l);
        }

        if (filter) {
          var u = rh.parentElement.textContent.match(author_re);
          if (u && u[1] && ignore_list.get(u[1]))
            elm.style.display = 'none';
        }
      }
    };
    block.watch('ready', function() {
      if (ftl.product) {
        // Wait for thread list to load before mangling
        var div = document.getElementById('forum');
        if (div !== null)
          new MutationObserver(function(es, mo) {
            mo.disconnect();
            mangle_list();
          }).observe(div, { childList: true });
      } else {
        mangle_list();
      }
    });
  }
}
if (!ftl.mobile && ftl.thread) {
  var have_itmenu = false;
  var have_itmanage = false;
  var update_itmanage = function(n, itm) {
    if (!have_itmenu)
      return;
    var div = document.getElementById('IgnorelistPopout');
    if (div === null)
      return;

    if (!have_itmanage && itm) {
      var frag = document.createDocumentFragment();
      var hr = document.createElement('hr');
      hr.className = 'itmanage';
      frag.appendChild(hr);

      var l = document.createElement('div');
      l.innerHTML = '<b>Ignored Users</b>';
      l.className = 'itmanage';
      l.style.marginTop = '10px';
      manage_menu.register(l);
      l.getElementsByClassName('il_addbtn')[0].className = 'FormButton blue';
      frag.appendChild(l);
      div.appendChild(frag);
      have_itmanage = true;
    } else if (have_itmanage) {
      var elms = div.getElementsByClassName('itmanage');
      for (var i = 0; i < elms.length; i++)
        elms[i].style.display = itm ? '' : 'none';
    }
  };
  var update_itmenu = function(n, itm) {
    if (!have_itmenu && itm) {
      var b = document.getElementsByClassName('ThreadCommandBar')[0];
      var sp = document.createElement('span');
      sp.id = 'Ignorelist';
      sp.className = 'ThreadCommand';
      sp.innerHTML = 'Settings';
      b.appendChild(sp);

      block.watch('late', function() {
        var div = document.createElement('div');
        div.id = 'IgnorelistPopout';
        div.className = 'PopoutPanel';
        div.align = 'left';
        div.setAttribute('style',
            'position: absolute; background: white; padding: 20px');
        settings_menu.register(div, thread_menu);
        b.appendChild(div);

        PM2({par: 'Ignorelist', dupe: false});

        have_itmenu = true;
        block.watch('late', function() {
          update_itmanage(undefined, settings.get('inthread_manage'));
        });
      });
    } else if (have_itmenu) {
      var elm = document.getElementById('Ignorelist');
      if (elm !== null)
        elm.style.display = itm ? '' : 'none';
    }
  };

  post_filter.watch(gen_watch_filter(function(post) {
    var tbl = post.post_elms[0].parentElement;
    var ph = post.placeholder = document.createElement('tr');
    ph.innerHTML = '<td colspan="2" class="ForumHeader" ' +
        'style="padding: 10px; text-align: center"><a>Post by ' +
        strip_tags(post.user) + ' hidden.  Click to show.</a></td>';
    ph.firstElementChild.firstElementChild.href = 'javascript:void(0)';
    handlers.hide_toggle(ph.firstElementChild.firstElementChild, post);
    tbl.insertBefore(ph, post.post_elms[0]);
    return ph;
  }));
  settings.watch('placeholders', post_filter.update);

  block.watch('ready', function() {
    // Wire posts & ignorebuttons up
    ignore_buttons.set('+ ignore', '+ unignore');
    for (var i = 0; i < posts.length; i++) {
      var post = posts[i];
      var tl = post.content_elm.parentElement.parentElement.parentElement;
      post.post_elms = [tl.previousElementSibling, tl];
      var head = tl.previousElementSibling.firstElementChild.lastElementChild;
      var new_a = document.createElement('a');
      new_a.href = 'javascript:void(0)';
      head.appendChild(new_a);
      post.ignbtn = new_a;
      post.badge = tl.getElementsByClassName('Badges')[0];
    }
    post_filter.register(posts);
    ignore_buttons.register(posts);
    user_tags.register(posts);

    settings.watch('inthread_manage', update_itmanage);
    settings.watch('inthread_menu', update_itmenu);
  });

  block.watch('late', function() {
    var p = parse_pager(document.getElementsByClassName('Pager')[1]);
    var lpn = (p.cur - 1) * 10 + posts.length;
    var lp = parseInt(posts[posts.length - 1].content_elm.id.slice(4));
    if (!ftl.perma)
      last_viewed.set(thread_id, p.cur, lpn, lp);
  });

  if (!ftl.perma && thread_id !== undefined) {
    if (location.hash !== '')
      scroll_to_hash();
  }
}
if (ftl.mobile && ftl.thread) {
  post_filter.watch(gen_watch_filter(function(post) {
    var tbl = post.post_elms[0].parentElement;
    var ph = post.placeholder = document.createElement('div');
    ph.className = 'col-xs-12 ListRow';
    ph.style.paddingBottom = '5px';
    ph.style.textAlign = 'center';
    ph.innerHTML = '<a>Post by ' + strip_tags(post.user) +
        ' hidden.  Click to show.</a>';
    ph.firstElementChild.href = 'javascript:void(0)';
    handlers.hide_toggle(ph.firstElementChild, post);
    tbl.insertBefore(ph, post.post_elms[0]);
    return ph;
  }));
  settings.watch('placeholders', post_filter.update);

  ignore_buttons.set(' Ignore', ' Unignore');

  var have_itmenu = false;
  var have_itmanage = false;
  var update_itmanage = function(n, itm) {
    if (!have_itmenu)
      return;
    var menu = document.getElementById('hideSettings');
    if (menu === null)
      return;
    if (!have_itmanage && itm) {
      var frag = document.createDocumentFragment();
      var inner = menu.getElementsByClassName('panel-body')[0];
      var hr = document.createElement('hr');
      hr.className = 'itmanage';
      frag.appendChild(hr);
      var l = document.createElement('div');
      l.className = 'itmanage';
      l.innerHTML = '<b>Ignored Users</b>';
      l.style.marginTop = '10px';
      manage_menu.register(l);
      l.getElementsByClassName('il_addbtn')[0].remove();
      frag.appendChild(l);
      inner.appendChild(frag);
      have_itmanage = true;
    } else if (have_itmanage) {
      var elms = menu.getElementsByClassName('itmanage');
      for (var i = 0; i < elms.length; i++)
        elms[i].style.display = itm ? '' : 'none';
    }
  };
  var update_itmenu = function(n, itm) {
    if (!have_itmenu && itm) {
      var div = document.createElement('div');
      div.id = 'itmenu';
      div.className = 'panel panel-default';
      div.innerHTML = '<div class="panel-heading"><h5 class="panel-title">' +
          '<a href="#hideSettings" data-toggle="collapse">Settings ' +
          '<span class="caret"></span></a></h5>' +
          '<div id="hideSettings" class="panel-collapse collapse">' +
          '<div class="panel-body"></div></div>';
      block.watch('late', function() {
        var inner = div.getElementsByClassName('panel-body')[0];
        settings_menu.register(inner, thread_menu);
        document.getElementsByClassName('body-content')[0].appendChild(div);

        have_itmenu = true;
        if (settings.get('inthread_manage') !== undefined)
          update_itmanage(undefined, settings.get('inthread_manage'));
      });
    } else if (have_itmenu) {
      document.getElementById('itmenu').style.display = itm ? '' : 'none';
    }
  };

  block.watch('ready', function() {
    for (var i = 0; i < posts.length; i++) {
      var post = posts[i];
      var pe = post.content_elm.parentElement;
      post.post_elms = [pe];
      var menu = pe.firstElementChild.lastElementChild;

      var li = document.createElement('li');
      li.setAttribute('role', 'presentation');
      li.innerHTML = '<a tabindex="-1" role="menuitem">' +
          '<span class="glyphicon glyphicon-plus red"></span><span></span></a>';
      li.firstElementChild.href = 'javascript:void(0)';
      var rp = menu.lastElementChild;
      if (!rp.firstElementChild.lastChild.data.startsWith(' Report'))
        rp = rp.previousElementSibling;
      menu.insertBefore(li, rp);
      post.ignbtn = li.firstElementChild.lastElementChild;

      var badge = menu.firstElementChild.firstElementChild;
      var m = badge.innerHTML.match(/(.*)\((.*)\)/);
      badge.innerHTML = m[1] + '(<span>' + m[2] + '</span>)';
      post.badge = badge.lastElementChild;
    }
    post_filter.register(posts);
    ignore_buttons.register(posts);
    user_tags.register(posts);

    settings.watch('inthread_manage', update_itmanage);
    settings.watch('inthread_menu', update_itmenu);
  });

  block.watch('late', function() {
    var p = parse_pager(document.getElementsByClassName('pagination')[0]);
    var lpn = (p.cur - 1) * 10 + posts.length;
    var lp = parseInt(posts[posts.length - 1].content_elm.id.slice(4));
    if (!ftl.perma)
      last_viewed.set(thread_id, p.cur, lpn, lp);
  });

  if (!ftl.perma && thread_id !== undefined) {
    if (location.hash !== '')
      scroll_to_hash();
  }
}
if (ftl.new_arrivals) {
  var product_filter = (function() {
    var items;

    function update() {
      if (items === undefined)
        return;

      for (var i = 0; i < items.length; i++) {
        items[i].content_elm.style.display =
            ignore_categories.get(items[i].category) ? 'none' : '';
      }
    }

    return {
      register: function(list) {
        items = list;
        ignore_categories.watch('late', update);
      },
    };
  })();

  var catnr_re = new RegExp('categor(y|ies)/([0-9]+)/');
  var catstr_re = new RegExp('\\bf=([a-zA-Z0-9]+)');
  var catstr_cleanup_re = new RegExp('^.*=([0-9]+)$');
  var ignore_category = function(elm, cat) {
    elm.addEventListener('click', function() { ignore_categories.set(cat); });
    ignore_categories.watch('late', function(c, s) {
      if (c === cat)
        elm.checked = !s;
    });
    elm.checked = !ignore_categories.get(cat);
  };
  var add_ign_cb = function(elm) {
    try {
      var cat = atob(elm.lastElementChild.href.match(catstr_re)[1])
          .match(catstr_cleanup_re)[1];

      var cb = document.createElement('input');
      cb.type = 'checkbox';
      cb.style.float = 'right';
      cb.style.margin = '2px -2px 0px -11px';
      ignore_category(cb, cat);
      elm.insertBefore(cb, elm.firstElementChild);
    } catch (e) {}
  };

  block.watch('ready', function() {
    var container = ftl.mobile ?
        document.getElementsByClassName('ProductsGrid')[0] :
        document.getElementById('Products_Grid');
    if (container === null || container === undefined)
      return;

    var list = [];
    for (var elm = container.firstElementChild; elm !== null;
        elm = elm.nextElementSibling) {
      try {
        var cat = elm.lastElementChild.lastElementChild.firstElementChild.href
            .match(catnr_re)[2];
        list.push({ content_elm: elm, category: cat });
      } catch (e) {}
    }
    product_filter.register(list);

    var cats, i;
    if (ftl.mobile) {
      var heads = document.getElementsByClassName('panel-heading');
      for (i = 0; i < heads.length; i++) {
        if (heads[i].textContent.trim() === 'Category') {
          cats = heads[i];
          break;
        }
      }
      if (cats === undefined)
        return;

      cats = cats.parentElement.lastElementChild.firstElementChild;
      while (cats !== null) {
        add_ign_cb(cats.lastElementChild);
        cats = cats.nextElementSibling;
      }
    } else {
      var groups = document.getElementsByClassName('FilterGroup');

      for (i = 0; i < groups.length; i++) {
        if (groups[i].textContent.trim() === 'Category') {
          cats = groups[i];
          break;
        }
      }
      if (cats === undefined)
        return;

      cats = cats.nextElementSibling;
      while (cats !== null && cats.classList.contains('FilterEntry')) {
        add_ign_cb(cats);
        cats = cats.nextElementSibling;
      }
    }
  });
}
// Mobile reviews don't show tags
if (ftl.reviews && !ftl.mobile) {
  var reg_tags = function(badges) {
    var posts = [];
    for (var i in badges) {
      var badge = badges[i];
      if (badge.innerText === '' ||
          !badge.previousElementSibling ||
          badge.previousElementSibling.className !== 'Nickname')
        continue;
      posts.push({
        user: badge.previousElementSibling.innerText.trim(),
        badge: badge,
      });
    }
    if (posts.length !== 0)
      user_tags.register(posts);
  };

  block.watch('ready', function() {
    var badges = document.getElementsByClassName('Badges');
    if (badges.length !== 0) {
      reg_tags(badges);
    } else {
      var elm;
      if (!ftl.mobile)
        elm = document.getElementById('Reviews-tab');
      else
        elm = document.getElementById('reviews');
      if (elm !== null) {
        new MutationObserver(function(es, mo) {
          mo.disconnect();
          reg_tags(document.getElementsByClassName('Badges'));
        }).observe(elm, { childlist: true });
      }
    }
  });
}
if (!ftl.mobile) {
  if (ftl.prsearch) {
    var search = location.href.match(/\/search(\/([0-9]+)?-?(.*))?\?([^&]+)/);
    if (search !== null) {
      if (!search[2])
        search[2] = '0';
      var sortcookie = document.cookie.match(/\bsort=([a-zA-Z0-9]+)/);
      sortcookie = sortcookie ? sortcookie[1] : 'r';
      location.replace(location.origin +
            '/category/' + search[2] + '/search/-/p/0?f=-&keywords=' + search[4] +
            '&sort=' + sortcookie);
    }
  }

  if (ftl.prcategory && location.search) {
    block.watch('ready', function() {
      var sortelm = document.getElementById('content_SortOrder');
      if (sortelm) {
        sortelm.onchange = function() {
          document.cookie = 'sort=' + this.value + ';path=/';
          location.href = location.href.replace(/\bsort=[^&]*/, '') +
              '&sort=' + this.value;
        };
      }

      var searchcat = document.getElementById('searchBarCategory');
      var urlcat = location.search.match(/\bf=([a-zA-Z0-9]+)/);
      if (searchcat !== null && urlcat !== null) {
        searchcat.value = atob(urlcat[1]).replace(/^.*=/, '');
        if (!searchcat.value)
          searchcat.value = '';
      }
    });
  }
} else {
  if (ftl.prsearch) {
    var search = location.href.match(/\/search\/([0-9]+).*\?(.*)/);
    if (search !== null) {
      var sortcookie = document.cookie.match(/\bsort=([a-zA-Z0-9]+)/);
      sortcookie = sortcookie ? sortcookie[1] : '0';
      location.replace(location.origin +
          '/c/' + search[1] + '/-/-/p/0?f=-&sort=' + sortcookie +
          '&' + search[2]);
    }
  }

  if (ftl.prcategory) {
      var fix_onclick = function(elm, sortnr) {
        elm.onclick = function() {
          document.cookie = 'sort=' + sortnr + ';path=/';
          location.href = location.href.replace(/\bsort=[^&]*/, '') +
              '&sort=' + sortnr;
        };
      };

      block.watch('ready', function () {
        try {
          var li = document.querySelector('.clearfix button .glyphicon-sort')
              .parentElement.parentElement.lastElementChild.firstElementChild;
          for ( ; li !== null; li = li.nextElementSibling) {
            console.log(li);
            var inner = li.firstElementChild;
            var sortnr = String.match(inner.onclick, /\bsort=([0-9]+)/);
            if (sortnr === null)
              continue;
            fix_onclick(inner, sortnr[1]);
          }
        } catch (e) {}
      });
  }
}
if (ftl.settings) {
  block.watch('ready', function() {
    var panel = document.getElementsByClassName('PageContentPanel')[0];
    var div = document.createElement('div');
    div.style.marginTop = '5px';
    div.innerHTML = '<table style="width: auto"><tbody><tr><td class="MediumL' +
        'abel Bold EndOfInlineSection" style="padding: 5px">Ignored Users</td' +
        '><td width=15></td><td class="MediumLabel Bold EndOfInlineSection" s' +
        'tyle="padding: 5px">FastTech Forum Enhancements Settings</td></tr><t' +
        'r><td style="padding: 0px"><div class="BGShadow" style="padding: 10p' +
        'x"></div></td><td width=15></td><td style="padding:10px"><div class=' +
        '"BGShadow" style="padding: 0px"></div></td></tr></tbody></table>';

    var divs = div.getElementsByTagName('div');
    settings_menu.register(divs[1], full_menus);
    manage_menu.register(divs[0]);
    divs[0].getElementsByClassName('il_addbtn')[0].className =
        'FormButton blue';
    panel.appendChild(div);
  });
}