// ==UserScript==
// @name FastTech Ignore List (BETA)
// @namespace ftil
// @description Ignore users on the FastTech forums, and more
// @include https://*.fasttech.com/*
// @version 2.1.0
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-end
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNvyMY98AAAFvSURBVFjD7Zc9S8NAGMcDvnSUggjFkoTiJA6u+gGk2Nmhg4Ozk10Ul+s5lg65l95LxC8QaC/Uoh/Fwc3RyW9Q79RIK5hwGI1iDv4QAnfP7//cc8k9jlP0wFgEGHNpLco5QqiygW4q/oXiXnckrQVV4BAiOlpTQu2ECO8lJvRCPQ/GU2uBUSfJQsu4QkSEWdLOTQbaHzOpHbX1osZVmMiF6u4t2MPse+PehXHr27f3NWA89aG6LaS+/gZAwNiWLqpHW2Ei7s0p+DIApeG27QnAhD8NBmIvlwzMARBxhKhopgqJJmNsLbctmAVg7Krx4zVQApQAUsolhMK6EQBgMU8A92xSXQfDeg2MV60n66NWNVD9vkyffBAtmCBGmyBazu+eQPnly0eHiutUl+fDWvJ7dYHaLQFKgBKgBPjfAB4c7xQL0I3juQYkW0HOGciW//6sDPDJpwsjyg5NG6YvqsdpAI3TaMW+KY2FB9S+8xvGM2SF001PVSN6AAAAAElFTkSuQmCC
// ==/UserScript==
'use strict';
var settings = (function() {
var settings = { };
var watches = { };
function inner_set(name, value) {
if (settings[name].value === 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;
}
window.addEventListener('storage', function(e) {
if (e.key in settings)
inner_set(e.key, JSON.parse(e.newValue));
});
return {
get: function(name) { return settings[name].value },
set: function(name, value) {
if (inner_set(name, value))
localStorage.setItem(name, JSON.stringify(value));
},
all: function() { return settings },
register: function(name, desc, def, dep) {
var val = localStorage.getItem(name);
if (val === null)
val = GM_getValue(name, null);
if (val !== null)
val = JSON.parse(val);
else
val = def;
settings[name] = {
value: val,
desc: desc,
dep: dep,
};
this.set(name, val);
},
watch: function(name, func) {
if (!(name in watches))
watches[name] = [];
watches[name].push(func);
if (settings[name] !== undefined)
func(name, settings[name].value);
},
};
})();
// Workaround for AMO warnings about javascript in innerHTML
function fix_as(e) {
var as = e.getElementsByTagName('a');
for (var i = 0; i < as.length; i++) {
if (!as[i].hasAttribute('href'))
as[i].href = 'javascript:void(0)';
}
}
// 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;
}
})();
/* Block functions that need the entire state set up before running. In
* particular, this is used by post_filter to avoid repeatedly filtering posts.
*/
var block = (function() {
var block = 1;
var watches = [];
return {
get: function() { return block !== 0 },
block: function() { block++ },
unblock: function() {
if (!(--block)) {
for (var i = 0; i < watches.length; i++)
watches[i]()
delete this.block
delete this.unblock;
this.watch = function(func) { func() };
}
},
watch: function(func) { watches.push(func) },
};
})();
// Assorted generic handlers for user input.
var handlers = (function() {
return {
// A click on elm toggles the ignored state of user
ignore_toggle: function(elm, user) {
elm.setAttribute('username', strip_tags(user));
elm.addEventListener('click', function() { ignore_list.set(user) });
},
// A click on elm toggles the state of setting; setting changes update elm
checkbox: function(elm, setting) {
elm.addEventListener('change',
function() { settings.set(setting, this.checked) });
settings.watch(setting, function(name, val) {
elm.checked = val;
});
},
// Handle enter keypress on manage_menu's text box
addbox_enter: function(elm) {
elm.addEventListener('keydown', function(k) {
if (k.which !== 13)
return true;
k.stopPropagation();
k.preventDefault();
ignore_list.set(strip_tags(this.value), true);
this.value = '';
return false;
});
},
// Handle click on manage_menu's add button
addbox_click: function(elm, tgt) {
elm.addEventListener('click', function() {
ignore_list.set(strip_tags(tgt.value), true);
tgt.value = '';
});
},
top_click: function(elm) {
elm.addEventListener('click', function() { scrollTo(0, 0) });
},
hide_toggle: function(elm, post) {
elm.addEventListener('click', function() { unhide_posts.set(post) });
},
};
})();
// Manage the state of the ignore list
var ignore_list = (function() {
settings.register('ignorelist', null, [], null);
// Use two stages so that filters can handle updates before being called
var watches = { 'early': [], 'late': [] };
var il = [];
// Case-insensitive indexOf (needed in case of case errors in user input)
function ci_indexof(a, n) {
for (var i = 0; i < a.length; i++) {
if (a[i].toLowerCase() === n.toLowerCase())
return i;
}
return -1;
}
function fire_watches(stage, user, state, idx) {
var w = watches[stage];
for (var i = 0; i < w.length; i++)
w[i](user, state, idx);
}
// In order to support userscripts, we need to rebuild the list on every set
function inner_set(user, state, idx) {
var nl = [];
var tmp = settings.get('ignorelist');
for (var i = 0; i < tmp.length; i++)
nl.push(tmp[i]);
if (state)
nl.push(user);
else
nl.splice(idx, 1);
settings.set('ignorelist', nl);
}
// Watch for ignorelist changes, and fire incremental watch events
settings.watch('ignorelist', function(name, val) {
var 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('early', user, state, idx);
fire_watches('late', user, state, idx);
// Userscript compat: handle multiple changes from other tabs
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 = [];
function update() {
if (block.get()) return;
for (var i = 0; i < posts.length; i++) {
var post = posts[i];
var hide = false;
for (var 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 (var j = 0; j < post.post_elms.length; j++)
post.post_elms[j].style.display = display;
for (var j = 0; j < watches.length; j++)
watches[j](post, hide);
}
}
return {
// Register a list of posts to be filtered
register: function(list) {
posts = list;
block.watch(update);
},
/* Add a new filter. Must be called before the unblock. Filters return
* true (should hide), false (must not hide), or undefined (no judgement).
*/
filter: function(func) { filters.push(func) },
// Run all filters again
update: update,
/* Call func with changes to the hidden status of each post (true ==
* hidden). Must be called before the unblock.
*/
watch: function(func) { watches.push(func) },
};
})();
// General-purpose built-in filters:
// Hide posts by ignored users
post_filter.filter((function() {
settings.watch('hide_ignored', post_filter.update);
settings.register('hide_ignored', 'Hide posts by ignored users', true, null);
ignore_list.watch('late', post_filter.update);
return function(post) {
if (settings.get('hide_ignored') && ignore_list.get(post.user))
return true;
}
})());
// Temporarily unhide hidden posts
var unhide_posts = (function() {
var posts = [];
post_filter.filter(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();
}
};
})();
// Generic buttons to toggle ignored state of a user
var ignore_buttons = (function() {
var ignbtns = { };
var inner_ign = '';
var inner_unign = '';
function inner_update(user, state) {
var elms = ignbtns[user];
if (elms === undefined)
return;
var fmt = inner_ign;
if (state)
fmt = inner_unign;
for (var i = 0; i < elms.length; i++)
elms[i].innerHTML = fmt;
}
function update_btns(users, state) {
if (users instanceof Array) {
for (var i = 0; i < users.length; i++)
inner_update(users[i], state);
} else {
inner_update(users, state);
}
}
return {
// Set the (HTML) format of the (un)ignore buttons
set: function(ign, unign) {
inner_ign = ign;
inner_unign = unign;
},
// Register an element to serve as an ignore button for user
register: function(user, elm) {
if (ignbtns[user] === undefined)
ignbtns[user] = [];
ignbtns[user].push(elm);
handlers.ignore_toggle(elm, user);
elm.innerHTML = inner_ign;
},
// Wire ignore_buttons to ignore_list to keep all buttons updated
update: function() { ignore_list.watch('late', update_btns) }
};
})();
// Handle RES-style tagging of users
var user_tags = (function() {
settings.register('user_tags', null, { }, null);
var badges = [];
function update_badges(n, val) {
for (var i = 0; i < badges.length; i++) {
var b = badges[i];
var t = val[b.user];
if (t === undefined)
t = b.saved;
for (var j = 0; j < b.elms.length; j++)
b.elms[j].innerHTML = strip_tags(t);
}
}
// Return a function to prompt for a new tag for user
function gen_prompt(user) {
return (function(e) {
e.preventDefault();
e.stopPropagation();
var tmp = 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 elm as the element showing user's tag
register: function(user, elm) {
user = strip_tags(user);
var i;
for (i = 0; i < badges.length; i++) {
if (badges[i].user === user)
break;
}
if (i === badges.length)
badges.push({ user: user, saved: elm.innerHTML,
func: gen_prompt(user), elms: [] });
var b = badges[i];
b.elms.push(elm);
elm.addEventListener('click', b.func);
},
// Wire user_tags to settings to track changes
update: function() { settings.watch('user_tags', update_badges) },
};
})();
// Track the last viewed page and post count for threads
var last_viewed = (function() {
var db;
var store = 'thread_data';
// Accumulate commands and fire them once idb is open
var cmds = [];
function run_cmds() {
if (db === undefined)
return;
var cmd;
while (cmd = cmds.shift()) {
if (cmd.cmd === 'set') {
delete cmd.cmd;
var tr = db.transaction(store, 'readwrite');
var os = tr.objectStore(store);
var req = os.get(cmd.id);
req.onsuccess = (function(os, cmd) {
return function(e) {
var t = e.target.result;
if (t === undefined) {
t = cmd;
} else {
t.lvt = cmd.lvt;
t.lvp = cmd.lvp;
if (cmd.lpn > t.lpn) {
t.lpn = cmd.lpn;
t.lp = cmd.lp;
}
}
os.put(t);
}
})(os, cmd);
} else if (cmd.cmd === 'get') {
var os = db.transaction(store, 'readonly').objectStore(store);
os.get(cmd.id).onsuccess = (function(cmd) {
return function(e) {
var t = e.target.result;
if (t === undefined)
return;
cmd.f(cmd.e, t.lvp, t.lpn, t.lp);
}
})(cmd);
}
}
}
/* Periodically scrub the database (FIXME this is awful & may run in multiple
* tabs simultaneously)
*/
settings.register('lv_scrub_time', null, 0, null);
// Open idb and maybe remove old threads
(function() {
var idb = indexedDB;
var req = idb.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();
setTimeout(function() {
var now = Date.now();
// This is awful, but it's better than scrubbing every page load.
if (settings.get('lv_scrub_time') + 24 * 60 * 60 * 1000 > now)
return;
var tr = db.transaction(store, 'readwrite');
var os = tr.objectStore(store);
os.index('lvt')
.openCursor(IDBKeyRange.upperBound(now - 14 * 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);
}
};
}, 5000);
};
})();
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({
cmd: 'set',
id: thread_id,
lvt: Date.now(),
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({ cmd: 'get', id: thread_id, f: func, e: extra });
run_cmds();
},
};
})();
// Generic settings menu
var settings_menu = (function() {
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();
for (var i = 0; i < menus.length; i++) {
var menu = menus[i];
var d;
if (i !== 0)
elm.appendChild(document.createElement('hr'));
if (menu.title !== undefined) {
d = elm.appendChild(document.createElement('div'));
d.innerHTML = '<b>' + menu.title + '</b>';
}
for (var j = 0; j < menu.vars.length; j++) {
var v = menu.vars[j];
d = elm.appendChild(document.createElement('div'));
d.innerHTML = '<input type="checkbox"></input><label></label>';
var e = d.children[0];
handlers.checkbox(e, v);
if (s[v].dep !== null) {
var ds = s[v].dep.match(/(!)?(.*)/);
settings.watch(ds[2], (function(e, i) {
return function(n, v) { e.disabled = v ^ i }
})(e, !ds[1]));
}
e.id = 'ilcb_' + v;
d.children[1].innerHTML = s[v].desc;
d.children[1].setAttribute('for', 'ilcb_' + v);
}
}
}
};
})();
// Display a nice menu for managing the ignorelist
var manage_menu = (function() {
var menu_list;
var menu_elms = [];
var remfmt = '';
function inner_update(user, state) {
var i;
for (i = 0; i < menu_elms.length; i++) {
// Keep menu alphabetical
var cmp = user.toLowerCase()
.localeCompare(menu_elms[i].user.toLowerCase());
if (cmp === 0) {
if (!state) {
menu_elms[i].elm.remove();
menu_elms.splice(i, 1);
}
return;
} else if (cmp === -1) {
break;
}
}
if (state) {
var tr = document.createElement('tr');
tr.innerHTML = '<td style="padding: 0px">' + user + '</td>' +
'<td align="right" style="padding: 0px"><a username=' + user + '>' +
remfmt + '</a></td>';
fix_as(tr);
handlers.ignore_toggle(tr.getElementsByTagName('a')[0], user);
if (i < menu_elms.length) {
menu_list.insertBefore(tr, menu_elms[i].elm);
menu_elms.splice(i, 0, { user: user, elm: tr });
} else {
menu_list.appendChild(tr);
menu_elms.push({ user: user, elm: tr});
}
}
}
function update(user, state) {
if (menu_list === undefined)
return;
if (user instanceof Array) {
for (var i = 0; i < user.length; i++)
inner_update(user[i], true);
} else {
inner_update(user, state);
}
}
return {
// Set the format of the remove/unignore button
set: function(fmt) { remfmt = fmt },
// Attach management elements to elm
register: function(elm) {
var tbl = elm.appendChild(document.createElement('table'));
tbl.innerHTML = '<tbody><tr><td colspan="2" style="padding: 0px">' +
'<input type="text" placeholder="Add user..." width="auto">' +
'</input> <div class="il_addbtn">Add</div></td></tr></tbody>';
menu_list = tbl.children[0];
var td = menu_list.children[0].children[0];
handlers.addbox_enter(td.children[0]);
handlers.addbox_click(td.children[1], td.children[0]);
ignore_list.watch('late', update);
},
};
})();
// Where are we?
// N.B. Permalink URLs are matched by both thread and perma.
var ftl = {
mobile: '^https?://m\\.',
thread: '^https?://(m|www)\\.fasttech\\.com/forums/[^/]+/t/[0-9]+/.',
perma: '^https?://(m|www)\\.fasttech\\.com/forums' +
'/[^/]+/t/[0-9]+/[^/]+\\?[0-9]+$',
threadlist: '^https?://(m|www)\\.fasttech\\.com/forums' +
'(/[^/]+(/page/[0-9]+)?(/search\\?.*)?)?$',
author: '^https?://(m|www)\\.fasttech\\.com/forums/author/',
product: '^https?://(m|www)\\.fasttech\\.com/p(roducts?)?/',
settings: '^https?://www\\.fasttech\\.com/forums/settings$',
};
for (var k in ftl)
ftl[k] = new RegExp(ftl[k]).test(location.href);
// Menu configuration
var full_menus = [
{ title: 'General', vars: ['track_viewed', 'track_posts', 'always_unread',
'hide_threads', '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: ['quote_quotes', 'quote_imgs'] },
{ title: 'Post Hiding',
vars: ['hide_ignored', 'hide_quotes', 'hide_deleted'] },
];
} else {
thread_menu = full_menus;
}
manage_menu.set('<img alt="Remove" ' +
'src="https://www.fasttech.com/images/minus-button.png"></img></a></td>');
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('agree_tou', null, false, null);
settings.register('placeholders', 'Show placeholders for hidden posts', true,
null);
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_quotes', 'Hide posts quoting ignored users', false,
'hide_ignored');
settings.register('hide_deleted', 'Hide deleted posts', true, null);
settings.register('hide_threads', 'Hide threads by ignored users', false, null);
settings.register('quote_quotes', 'Include quotes in quotes', true, null);
settings.register('quote_imgs', 'Include images in quotes', true, null);
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;
}
}
})());
}
window.setTimeout(block.unblock, 0);
// Get all post bodies, desktop and mobile
function get_posts() {
var ret = [];
var posts = document.getElementsByClassName('PostContent');
for (var i = 0; i < posts.length; i++) {
var pc = posts[i];
var user = pc.getAttribute('data-username');
if (user !== null)
ret.push({ user: user, content_elm: pc });
}
return ret;
}
// Create a function to create placeholders as needed
function gen_watch_filter(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 the current and last pages from a pager element, desktop and mobile
function parse_pager(elm) {
var cur, last;
if (elm === undefined || elm === null) {
cur = last = 1;
} else if (!ftl.mobile) {
var sel = elm.getElementsByClassName('PageLink_Selected')[0];
if (sel !== undefined)
cur = parseInt(sel.innerHTML);
last = parseInt(elm.lastElementChild.previousElementSibling.innerHTML);
} else {
var 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 jtpbox = document.getElementById('JumpToPage');
if (jtpbox !== null) {
var lastpg = parse_pager(jtpbox.parentElement).last;
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', function(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;
});
}
}
}
}
// RE to pull a thread id from a URL
var thread_id_re = new RegExp('/forums/[^/]+/t/([0-9]+)/[^?]*$');
if (ftl.thread) {
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;
}
// Translate a post number into a page number, handling FT's off-by-one quirk
function postnr_to_pagenr(post, maxpage) {
return Math.floor(Math.min(maxpage, post / 10 + 1));
}
// Create a link to a specific page of a thread
function gen_page_link(base, pn, unread) {
var ret = base;
if (pn !== 1)
ret += '/' + pn;
if (unread === true)
ret += '#unread';
return ret;
}
// Find the reply button for a post
var replace_quotebtn;
// Add a "Top of page" button at the bottom of the page, aligned with the pager
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>';
fix_as(tbtn);
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);
}
};
}
if (ftl.thread) {
add_top_btn();
if (!ftl.mobile) {
// Persist the terms of use checkbox
var toubox = document.getElementById('AgreeTerms');
if (toubox !== null)
handlers.checkbox(toubox, 'agree_tou');
replace_quotebtn = function(elm) {
// Ugh.
var oldbtn = elm.parentElement.parentElement.parentElement
.previousElementSibling.firstElementChild.lastElementChild
.firstElementChild;
var newbtn = oldbtn.parentElement
.insertBefore(document.createElement('a'), oldbtn);
oldbtn.remove();
newbtn.innerHTML = 'quote/reply';
newbtn.href = 'javascript:void(0)';
return newbtn;
};
setTimeout(function() {
var ta = document.getElementsByClassName('mbbc-editor')[0];
if (ta !== undefined) {
// 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
ta.style.boxSizing = 'border-box';
ta.style.width = '100%';
// Add a YouTube button
var url_btn = document.getElementsByClassName('mbbc-url')[0];
if (url_btn !== undefined) {
var yt_btn = url_btn.parentElement
.insertBefore(document.createElement('li'), url_btn);
yt_btn.className = 'mbbc-youtube';
yt_btn.addEventListener('click', function() {
var sp = ta.selectionStart;
var ep = ta.selectionEnd;
var sel;
if (ep < sp) {
sp = ep;
ep = ta.selectionStart;
}
if (ep !== sp) {
sel = ta.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;
ta.value = ta.value.slice(0, sp) +
'[youtube]' + sel + '[/youtube]' +
ta.value.slice(ep);
ta.selectionStart = ta.selectionEnd = sp + 9 +
(sel === '' ? 0 : 10 + sel.length);
ta.focus();
});
}
}
}, 1000);
} else { // mobile
// 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);
}
// Fix up YT iframe size: CSS doesn't work well here.
var ifrs = document.getElementsByTagName('iframe');
for (var i = 0; i < ifrs.length; i++) {
var ifr = ifrs[i];
if (ifr.clientWidth > ifr.parentElement.clientWidth) {
var ar = ifr.clientHeight / ifr.clientWidth;
ifr.style.width = '100%';
ifr.style.height = Math.ceil(ifr.clientWidth * ar) + 'px';
}
}
replace_quotebtn = function(elm) {
// Ugh.
var oldbtn = elm.parentElement.firstElementChild.lastElementChild
.lastElementChild.firstElementChild;
var newbtn = oldbtn.parentElement
.insertBefore(document.createElement('a'), oldbtn);
newbtn.innerHTML = oldbtn.innerHTML;
oldbtn.remove();
newbtn.href = 'javascript:void(0)';
newbtn.tabindex = '-1';
newbtn.role = 'menuitem';
return newbtn;
};
}
}
/* 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
*/
document.body.appendChild(document.createElement('style')).innerHTML =
'.PostContent span[style*="text-align:"] {display:block;width:100%}' +
'a.SKUAutoLink {display=inline-block}';
// Re-implement quotes in a way that sort of works
var fix_quote_btns = (function() {
var textarea;
var editor;
var edit_panel;
var expand;
var thread_id;
function init(elms) {
if (!ftl.mobile)
textarea = document.getElementsByName('mbbc-editor')[0];
else
textarea = document.getElementById('RawContent');
if (!textarea || !replace_quotebtn)
return;
if (!ftl.mobile) {
editor = textarea.parentElement.parentElement;
} else {
edit_panel = document.getElementById('writePost');
if (edit_panel) {
editor = edit_panel.parentElement;
expand = edit_panel.previousElementSibling.firstElementChild
.firstElementChild;
}
}
var tid = location.href.match(new RegExp('/t/([0-9]+)/'));
if (tid !== null)
thread_id = tid[1];
for (var i = 0; i < elms.length; i++) {
var elm = elms[i];
var newbtn = replace_quotebtn(elm.content_elm);
newbtn.addEventListener('click', (function(elm) {
return (function() { quote(elm.user, elm.content_elm) })})(elm));
}
}
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 s = e.style;
var t = null;
var v = '';
if (s.textDecoration === 'underline') {
t = 'u';
} else if (s.textDecoration === 'line-through') {
t = 's';
} else if (s.fontSize !== '') {
t = 'size';
v = parseInt(s.fontSize);
} else if (s.color !== '') {
t = 'color';
v = s.color;
} else if (s.textAlign !== '') {
t = 'align';
v = s.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
tmp += '(snip)\n';
} 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 tag.
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 += wrap_tag('url', e.href, inner_quote(e, range, depth));
} else {
tmp += e.textContent;
}
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) {
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 (textarea.value.length > 0 &&
textarea.value[textarea.value.length - 1] !== '\n')
textarea.value += '\n';
if (thread_id !== undefined && post_id !== undefined)
textarea.value += wrap_tag('url',
'/forums/-/t/' + thread_id + '?' + post_id, user + ' wrote') + ':\n';
else
textarea.value += user + ' wrote:\n';
textarea.value += wrap_tag('quote', '',
inner_quote(elm, sel, 0).trim().replace(/[\r\n]{3,}/mg, '\n\n')) + '\n';
if (editor)
editor.scrollIntoView();
if (expand && edit_panel && edit_panel.className &&
!/\bin\b/.test(edit_panel.className))
expand.click();
}
return function(elms) {
// Delay fixing links until the editor has loaded. 1s should be enough.
setTimeout(function() { init(elms) }, 1000);
}
})();
function scroll_to_unread(tid, posts) {
last_viewed.get(tid, function(posts, lvp, lpn, lp) {
var i;
var pid = 'POST' + lp;
for (i = posts.length - 1; i >= 0; i--) {
if (posts[i].content_elm.id === pid)
break;
}
// Drop the #unread so we won't run again
history.replaceState({}, '', location.pathname);
// We've already seen all posts
if (i === posts.length - 1)
return;
i = Math.min(i + 1, posts.length - 1);
// scrollIntoView() doesn't work on hidden elements.
var elm = posts[i].post_elms[0];
var sd = elm.style.display;
elm.style.display = '';
elm.scrollIntoView();
elm.style.display = sd;
}, posts);
}
if (ftl.threadlist || ftl.product || ftl.author || ftl.settings) {
if (!ftl.mobile) {
if (!ftl.settings)
add_top_btn();
block.watch(function() {
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);
if (pelm === undefined) {
if (!ftl.settings) {
pelm = row.lastElementChild;
pelm.style.display = '';
pelm = pelm.appendChild(document.createElement('div'));
pelm.className = 'Pager';
} else {
pelm = row.parentElement.appendChild(document.createElement('div'));
pelm.className = 'Pager';
}
}
if (settings.get('track_viewed')) {
var lv = pelm.appendChild(document.createElement('span'));
lv.innerHTML = '<a href="' +
gen_page_link(elm.href, last_viewed_page) + '">Last viewed</a>';
lv.className = 'FieldFlag';
lv.style.padding = '7px 8px 5px';
lv.firstElementChild.style.fontSize = '10pt';
}
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 = pelm.appendChild(document.createElement('span'));
np.className = 'FieldFlag';
var npa = np.appendChild(document.createElement('a'));
if (unread) {
np.style.backgroundColor = '#AA0D0D';
npa.innerHTML = 'Unread posts';
npa.href = gen_page_link(elm.href,
postnr_to_pagenr(last_post_nr, p.last), true);
} else {
np.style.backgroundColor = '#F0F0F0';
npa.innerHTML = 'No unread posts';
npa.style.color = '#333';
}
np.style.padding = '7px 8px 5px';
npa.style.fontSize = '10pt';
}
};
var header = document.getElementsByClassName('ForumHeader')[0];
var filter = settings.get('hide_threads');
var sku_re = new RegExp('fasttechcdn.com/[0-9]+/([0-9]+)/.*');
if (header !== undefined) {
var tbl = header.parentElement;
for (var i = 1; i < tbl.children.length; i += 2) {
var tr = tbl.children[i];
var tl = tr.getElementsByClassName('ThreadLink')[0];
var tid;
if (tl !== undefined &&
(settings.get('track_posts') || settings.get('track_threads'))) {
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 a = img.parentElement
.appendChild(document.createElement('a'));
a.href = '/products/' + sku[1];
a.appendChild(img);
}
}
if (filter) {
var u = tl.parentElement.textContent.match(/started\W+by\W+(\w+)/);
if (u && u[1] && ignore_list.get(u[1])) {
tr.style.display = 'none';
if (tr.previousElementSibling !== header)
tr.previousElementSibling.style.display = 'none';
}
}
}
}
});
} else { // mobile
block.watch(function() {
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 = rh.appendChild(document.createElement('div'));
d.style.marginLeft = '-5px';
if (settings.get('track_viewed')) {
var lv = d.appendChild(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';
}
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 = d.appendChild(document.createElement('span'));
np.className = 'btn btn-default';
np.style.margin = '5px';
var npa = np.appendChild(document.createElement('a'));
if (unread) {
npa.innerHTML = 'Unread posts';
npa.href = gen_page_link(elm.href,
postnr_to_pagenr(last_post_nr, p.last), true);
npa.style.color = '#C64148';
} else {
npa.innerHTML = 'No unread posts';
npa.style.color = '#888';
}
}
};
var mangle_list = function() {
add_top_btn();
var filter = settings.get('hide_threads');
var elms = document.getElementsByClassName('ListRow');
for (i = 0; i < elms.length; i++) {
var elm = elms[i];
if (elm === undefined)
continue;
var rh = elm.getElementsByClassName('RowHeading')[0];
if (rh === undefined)
continue;
var l = rh.firstElementChild;
last_viewed.get(parseInt(l.href.match(thread_id_re)[1]),
add_last_viewed, l);
if (filter) {
var u = tl.parentElement.textContent.match(/started\W+by\W+(\w+)/);
if (u && u[1] && ignore_list.get(u[1]))
elm.style.display = 'none';
}
}
};
if (ftl.product) {
// Wait for thread list to load before mangling
var div = document.getElementById('forum');
if (div !== null)
(new MutationObserver(mangle_list)).observe(div, { childList: true });
} else {
mangle_list();
}
});
}
}
if (!ftl.mobile && ftl.thread) {
// Re-implement FT's PopoutMenu, only nicer
var PM2 = (function() {
var saved_class, saved_par, saved_ch, timeout;
function add_listener(elem, type, val, par, ch) {
elem.addEventListener(type, function() { act(val, par, ch) });
}
function add_listeners(elem, par, ch) {
add_listener(elem, 'mouseover', 1, par, ch);
add_listener(elem, 'mouseout', 0, par, ch);
}
function show() {
saved_par.className += ' focused';
saved_ch.style.display = 'inline';
// Hack: this is the easiest way to fix up the element locations.
saved_ch.style.left = saved_ch.style.top = '0px';
var pb = saved_par.getBoundingClientRect();
var cb = saved_ch.getBoundingClientRect();
var pad = parseInt(saved_ch.style.padding);
pad = pad ? pad : 0;
saved_ch.style.left = Math.min(pb.left - cb.left,
document.body.clientWidth - cb.width + pad) + 'px';
saved_ch.style.top = (pb.top - cb.top + 35) + 'px';
saved_ch.style.width = (cb.width - pad * 2) + 'px';
}
function hide() {
if (!saved_class) return;
saved_par.className = saved_class;
saved_ch.style.display = 'none';
saved_class = undefined;
}
function act(val, par, ch) {
if (val) {
clearTimeout(timeout);
if (saved_par != par)
hide();
saved_par = par;
saved_ch = ch;
if (saved_class === undefined) {
saved_class = par.className;
show();
}
} else {
timeout = setTimeout(function() { hide() }, 300);
}
}
// Hack: we can't remove anonymous event listeners, so duplicate elements
function dupe(id, raw) {
var elm = document.getElementById(id);
var nelm = document.createElement(elm.tagName);
nelm.align = elm.align;
if (raw) {
nelm.innerHTML = elm.innerHTML;
} else {
nelm.setAttribute('style', 'position: absolute; height: auto; ' +
'background-color: white; padding: 20px');
while (elm.childNodes[0])
nelm.appendChild(elm.childNodes[0]);
}
nelm.id = elm.id + '2';
nelm.className = elm.className;
elm.parentElement.insertBefore(nelm, elm);
elm.remove();
return nelm;
}
return function(par) {
var ch = dupe(par + 'Popout', false);
par = dupe(par, true);
add_listeners(par, par, ch);
add_listeners(ch, par, ch);
}
})();
var update_itmanage = function() {
var sp = document.getElementById('Ignorelist2');
if (sp === null)
return;
var div = document.getElementById('IgnorelistPopout2');
if (div === null)
return;
var elms = div.getElementsByClassName('itmanage');
switch ((elms.length === 0) * 2 + settings.get('inthread_manage')) {
case 3:
div.appendChild(document.createElement('hr')).className = 'itmanage';
var l = div.appendChild(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';
break;
case 1:
for (var i = 0; i < elms.length; i++)
elms[i].style.display = '';
break;
case 0:
for (var i = 0; i < elms.length; i++)
elms[i].style.display = 'none';
break;
}
};
settings.watch('inthread_manage', update_itmanage);
var update_itmenu = function() {
var sp = document.getElementById('Ignorelist2');
var div;
switch ((sp === null) * 2 + settings.get('inthread_menu')) {
case 3:
var b = document.getElementsByClassName('ThreadCommandBar')[0];
sp = b.appendChild(document.createElement('span'));
sp.id = 'Ignorelist';
sp.className = 'ThreadCommand';
sp.innerHTML = 'Settings';
div = b.appendChild(document.createElement('div'));
div.id = 'IgnorelistPopout';
div.className = 'PopoutPanel';
div.align = 'left';
settings_menu.register(div, thread_menu);
PM2('Ignorelist');
if (settings.get('inthread_manage') !== undefined)
update_itmanage();
break;
case 1:
sp.style.display = '';
break;
case 0:
sp.style.display = 'none';
break;
}
};
settings.watch('inthread_menu', update_itmenu);
PM2('RateThread');
PM2('ForumTools');
post_filter.watch(gen_watch_filter(function(post) {
var tbl = post.post_elms[0].parentElement;
var ph = post.placeholder = tbl
.insertBefore(document.createElement('tr'), post.post_elms[0]);
ph.innerHTML = '<td colspan="2" class="ForumHeader" ' +
'style="padding: 10px; text-align: center"><a>Post by ' +
strip_tags(post.content_elm.getAttribute('data-username')) +
' hidden. Click to show.</a></td>';
fix_as(ph);
handlers.hide_toggle(ph.firstElementChild.firstElementChild, post);
return ph;
}));
settings.watch('placeholders', post_filter.update);
// Wire posts & ignorebuttons up
ignore_buttons.set('+ ignore', '+ unignore');
var posts = get_posts();
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 = head.appendChild(document.createElement('a'));
new_a.href = 'javascript:void(0)';
ignore_buttons.register(post.user, new_a);
var badge = tl.getElementsByClassName('Badges')[0];
if (badge !== undefined)
user_tags.register(post.user, badge);
}
post_filter.register(posts);
ignore_buttons.update();
user_tags.update();
fix_quote_btns(posts);
if (!ftl.perma && thread_id !== undefined) {
if (location.hash === '#unread')
scroll_to_unread(thread_id, posts);
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));
last_viewed.set(thread_id, p.cur, lpn, lp);
}
}
if (ftl.mobile && ftl.thread) {
post_filter.watch(gen_watch_filter(function(post) {
var tbl = post.post_elms[0].parentElement;
var ph = post.placeholder =
tbl.insertBefore(document.createElement('div'), post.post_elms[0]);
ph.className = 'col-xs-12 ListRow';
ph.style.paddingBottom = '5px';
ph.style.textAlign = 'center';
ph.innerHTML = '<a>Post by ' +
strip_tags(post.content_elm.getAttribute('data-username')) +
' hidden. Click to show.</a>';
fix_as(ph);
handlers.hide_toggle(ph.firstElementChild, post);
return ph;
}));
settings.watch('placeholders', post_filter.update);
var posts = get_posts();
for (var i = 0; i < posts.length; i++)
posts[i].post_elms = [posts[i].content_elm.parentElement];
post_filter.register(posts);
fix_quote_btns(posts);
ignore_buttons.set(
'<span class="glyphicon glyphicon-plus red"></span> Ignore',
'<span class="glyphicon glyphicon-plus red"></span> Unignore');
var cont = document.getElementsByClassName('container-fluid')[0];
var menus = cont.getElementsByClassName('dropdown-menu');
for (var i = 0; i < menus.length; i++) {
var user = menus[i].getElementsByTagName('b')[0].innerHTML;
var li = document.createElement('li');
li.role = 'presentation';
li.innerHTML = '<a tabindex="-1" role="menuitem"></a>';
fix_as(li);
for (var j = 0; j < menus[i].children.length; j++) {
if (menus[i].children[j].innerHTML.match('Report'))
break;
}
menus[i].insertBefore(li, menus[i].children[j]);
ignore_buttons.register(user, li.firstElementChild);
var badge = menus[i].firstElementChild.firstElementChild;
var m = badge.innerHTML.match(/(.*)\((.*)\)/);
badge.innerHTML = m[1] + '(<span>' + m[2] + '</span>)';
user_tags.register(user, badge.lastElementChild);
}
ignore_buttons.update();
user_tags.update();
if (!ftl.perma && thread_id !== undefined) {
if (location.hash === '#unread')
scroll_to_unread(thread_id, posts);
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));
last_viewed.set(thread_id, p.cur, lpn, lp);
}
var update_itmanage = function() {
var menu = document.getElementById('hideSettings');
if (menu === null)
return;
var elms = menu.getElementsByClassName('itmanage');
switch ((elms.length === 0) * 2 + settings.get('inthread_manage')) {
case 3:
var inner = menu.getElementsByClassName('panel-body')[0];
inner.appendChild(document.createElement('hr')).className = 'itmanage';
var l = inner.appendChild(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();
break;
case 1:
for (var i = 0; i < elms.length; i++)
elms[i].style.display = '';
break;
case 0:
for (var i = 0; i < elms.length; i++)
elms[i].style.display = 'none';
break;
}
};
settings.watch('inthread_manage', update_itmanage);
var update_itmenu = function() {
var menu = document.getElementById('itmenu');
switch ((menu === null) * 2 + settings.get('inthread_menu')) {
case 3:
var div = document.getElementsByClassName('body-content')[0]
.appendChild(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>';
var inner = div.getElementsByClassName('panel-body')[0];
settings_menu.register(inner, thread_menu);
if (settings.get('inthread_manage') !== undefined)
update_itmanage();
break;
case 1:
menu.style.display = '';
break;
case 0:
menu.style.display = 'none';
break;
}
};
settings.watch('inthread_menu', update_itmenu);
}
if (ftl.settings) {
var div = document.getElementsByClassName('PageContentPanel')[0]
.appendChild(document.createElement('div'));
div.style.marginTop = '5px';
var tbl = div.appendChild(document.createElement('table'));
tbl.style.width = 'auto';
tbl.innerHTML = '<tbody><tr><td></td><td width="15"></td><td></td></tr>' +
'<tr><td></td><td></td><td></td></tr></tbody>';
var cells = tbl.getElementsByTagName('td');
cells[0].className = 'MediumLabel Bold EndOfInlineSection';
cells[0].style.padding = '5px';
cells[0].innerHTML = 'Ignored Users';
cells[2].className = 'MediumLabel Bold EndOfInlineSection';
cells[2].style.padding = '5px';
cells[2].innerHTML = 'FastTech Ignore List Settings';
cells[5].style.padding = '0px';
div = cells[5].appendChild(document.createElement('div'));
div.className = 'BGShadow';
div.style.padding = '10px';
settings_menu.register(div, full_menus);
cells[3].style.padding = '0px';
div = cells[3].appendChild(document.createElement('div'));
div.className = 'BGShadow';
div.style.padding = '10px';
manage_menu.register(div);
div.getElementsByClassName('il_addbtn')[0].className = 'FormButton blue';
}