您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ignore users on the FastTech forums, and more
当前为
// ==UserScript== // @name FastTech Forum Enhancements (BETA) // @namespace ftil // @description Ignore users on the FastTech forums, and more // @include https://*.fasttech.com/* // @version 2.2.0 // @grant GM_getValue // @grant GM_setValue // @run-at document-start // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB+klEQVRYw+2XPUscQRjHF+J7YWWhKDtLwJyIWmhnoU16r/YDREgRsLAysi+ije55OzPnzKRR1Car3gwXsAiBiI0fILWtqB8i57OeC3eKuDt3egr3wI+FLWb+PO9jGM02QngOY8Z02NrazlieyliOZDqYrswZmPIlEFEmNCWEnQoh2qe+iHbkyFPkqnJaTEct3XkBF0QWROxhwg+SAJcvB0HQGXsR2TtdyFHLyJUHtajLymXy4sH/PeTJ7IuHFzzz+17AblPy630IYIwNQ/z/6ZDPb4/VLQAOGk9fAbyMMd9oiAdqBBDuQX0vPAelfKZhIagWkC8UJl49B1oCWgKgp/diLLIRQbDf20gB5qqcjnq+aZcmnxZA2VwsIC2U0n5kh/3RJTpYbnEuCsFh6kZUGcdXm0L0DdilPtORVzrjGEJzaPi+3323YBB+DQff1ECqoNVfdkbIj0+xF63vKoOc4hnE/CYRrrqOFpKhxZ/dRsuabrkcH8CU+TrkYZR/9H4NQxL6WrhyXW8cVybneRiGHaN22AEHnestpXKlRgAm7A9k+HEC1mAj7om9CKXYAxvuGgg5TgIssEfIlvMv3ooTWUtAvQKQXRp5/CBJiFMU9XsgDD9AAp5ozIH/wFeYBWwwfmyCDep4cdb+22a56lviR6mnCuaq+vwmGuEte5DlWQaVMykAAAAASUVORK5CYII= // ==/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) { if (counters[name] === undefined) counters[name] = { val: 1, watches: [] }; else if (counters[name].val !== 0) counters[name].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]; c.watches.push(func); if (c.val === 0 && c.watches.length === 1) this.unblock(name); }, }; })(); /* 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 + '(.+)'); // 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; } function migrate(name) { var v = localStorage.getItem(name); if (v !== null) { GM_setValue(name, v); localStorage.removeItem(name); } } 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) { migrate(name); var val = GM_getValue(name, null); if (val !== null) return JSON.parse(val); else return settings[name].def; } function wrap_get(name) { if (settings[name].value === undefined) settings[name].value = get_sync(name); return settings[name].value; } function request() { for (var k in settings) { if (settings[k] === undefined) settings[k] = get_sync(k); } block.unblock('ready'); } return { get: wrap_get, get_sync: get_sync, set: function(name, value) { if (inner_set(name, value)) { var s = JSON.stringify(value); localStorage.setItem(prefix + name, s); GM_setValue(name, s); } }, all: function() { return settings }, register: function(name, desc, def, dep) { settings[name] = { desc: desc, def: def, dep: dep, }; }, watch: function(name, func) { if (!(name in watches)) watches[name] = []; watches[name].push(func); func(name, wrap_get(name)); }, update: request, }; })(); // 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() { return { // A click on elm toggles the ignored state of user ignore_toggle: function(elm, 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() { // 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); }); } // In order to support userscripts, we need to rebuild the list on every set function inner_set(user, state, idx) { if (settings.get_sync) il = settings.get_sync('ignorelist'); if (state) il.push(user); else il.splice(idx, 1); settings.set('ignorelist', il); fire_watches(user, state, idx); } var connect = function() { // Watch for ignorelist changes, and fire incremental watch events settings.watch('ignorelist', function(name, val) { if (il.length === val.length) return; if (il.length === 0) { for (var i = 0; i < val.length; i++) il.push(val[i]); fire_watches(il, true); return; } 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(user, state, idx); continue; } i++; j++; } }); connect = function() { }; }; 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) { connect(); watches[stage].push(func); if (il !== []) func(il, true, -1); }, update: function() { settings.register('ignorelist', null, [], null); }, }; })(); /* 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; 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); } 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) { if (users instanceof Array) { for (var 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 (var 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; } } function do_prompt(e, user) { e.preventDefault(); e.stopPropagation(); 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); } function gen_prompt(user) { return function(e) { do_prompt(e, user); } } 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; post.badge.addEventListener('click', gen_prompt(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'; // Accumulate commands and fire them once idb is open var cmds = []; function run_cmds() { if (db === undefined || db === null) { open_db(); return; } var cmd; while (cmd = cmds.shift()) { if (cmd.cmd === 'set') { delete cmd.cmd; var os = db.transaction(store, 'readwrite').objectStore(store); os.get(cmd.id).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); } } } // 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({ 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(); }, update: open_db, }; })(); // 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(); 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); html += '<div><input type="checkbox" id="ilcb_' + v + '></input>' + '<label for="ilcb_' + v + '>' + s[v].desc + '</label></div>'; } } d.innerHTML = html; var e = d.firstElementChild; var depre = /(!)?(.*)/; function h(e, v) { handlers.checkbox(e, v); var d = s[v].dep; if (d) { var ds = s[v].dep.match(depre); var i = !ds[1]; d = ds[2]; settings.watch(d, function(n, v) { e.disabled = v ^ 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 remfmt = ''; function create_elm(user) { 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>'; var a = tr.lastElementChild.firstElementChild; a.href = 'javascript:void(0)'; handlers.ignore_toggle(a, user); return { user: user.toLowerCase(), elm: tr }; } function find_elm(user) { var l = user.toLowerCase(); for (var i = 0; i < menu_elms.length; i++) { if (menu_elms[i].user.localeCompare(l) === 0) return i; } } function comp_elm(a, b) { return a.user.localeCompare(b.user) } function update(user, state) { if (menu_list === undefined) return; if (user instanceof Array) { var frag = document.createDocumentFragment(); menu_elms = user.map(create_elm).sort(comp_elm); for (var i = 0; i < menu_elms.length; i++) frag.appendChild(menu_elms[i].elm); menu_list.appendChild(frag); } else { if (state) { menu_elms.push(create_elm(user)); menu_elms.sort(comp_elm); var idx = find_elm(user); 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 { var idx = find_elm(user); menu_elms[idx].elm.remove(); menu_elms.splice(idx, 1); } } } return { // Set the format of the remove/unignore button set: function(fmt) { remfmt = fmt }, // Attach management elements to elm register: function(elm) { menu_list = document.createElement('table'); menu_list.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>'; var td = menu_list.firstElementChild.firstElementChild.firstElementChild; handlers.addbox_enter(td.firstElementChild); handlers.addbox_click(td.lastElementChild, td.firstElementChild); ignore_list.watch('late', update); elm.appendChild(menu_list); }, }; })(); // 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$', }; 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.perma) || (ftl.threadlist && !ftl.settings) || 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; if (ftl.thread || ftl.settings) { // 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('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) { ignore_list.update(); last_viewed.update(); if (ftl.thread) user_tags.update(); settings.update(); block.block('late'); block.watch('ready', //function() { block.watch('ready', function() { block.unblock('late') }); function() { window.setTimeout(function() { block.unblock('late') }, 0); }); } document.addEventListener('DOMContentLoaded', function() { block.unblock('ready') }); if (ftl.thread || ftl.editor) { /* 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%}' + 'a.SKUAutoLink {display=inline-block}'; block.watch('ready', function() { document.body.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; 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 fix_jtp = function() { 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; }); } } } }; 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, pn, unread) { var ret = base; if (pn !== 1) ret += '/' + pn; if (unread === true) ret += '#unread'; return ret; }; } // 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) { bbeeditor = bbetext.parentElement.parentElement; // 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.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 = bbepanel.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 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. 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) { 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)) expand.click(); } return function(elms, func) { for (var i = 0; i < elms.length; i++) { var elm = elms[i]; var newbtn = func(elm.content_elm); newbtn.addEventListener('click', (function(elm) { return (function() { quote(elm.user, elm.content_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 // Fix up YT iframe size: CSS doesn't work well here. var fr_resize = (function() { var frs; function handler() { for (var i = 0; i < frs.length; i++) { var fr = frs[i]; fr.elm.style.height = Math.ceil(fr.elm.clientWidth * fr.ar) + 'px'; } } return function(fr) { if (frs === undefined) { frs = []; window.addEventListener('resize', handler); } var ar = fr.clientHeight / fr.clientWidth; fr.style.maxWidth = fr.clientWidth + 'px'; fr.style.width = '100%'; fr.style.height = Math.ceil(fr.clientWidth * ar) + 'px'; frs.push({elm: fr, ar: ar}); }; })(); 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('ready', function() { var frs = document.getElementsByTagName('iframe'); for (var i = 0; i < frs.length; i++) fr_resize(frs[i]); }); 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.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') last_viewed.get(thread_id, function(nil, 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]); }); }, null); else if (location.hash !== '') block.watch('filter', function() { scroll(document.getElementById(location.hash)); }); // Drop the hash so we won't run again history.replaceState({}, '', 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 = 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 = document.createElement('a'); lv.href = gen_page_link(elm.href, last_viewed_page); lv.innerHTML = 'Last viewed'; lv.className = 'FormButton blue'; lv.style.color = '#fff'; 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'); np.style.marginLeft = '5px'; if (unread) { np.innerHTML = 'Unread posts'; np.className = 'FormButton red'; np.style.color = '#fff'; np.href = gen_page_link(elm.href, postnr_to_pagenr(last_post_nr, p.last), true); } else { np.innerHTML = 'No unread posts'; np.className = 'FormButton white'; } 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]+)/.*'); 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(/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'; } } } } }; 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), true); npa.style.color = '#C64148'; } else { npa.innerHTML = 'No unread posts'; npa.style.color = '#888'; } np.appendChild(npa); d.appendChild(np); } rh.appendChild(d); }; var mangle_list = function() { 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; 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 = tl.parentElement.textContent .match(/started\W+by\W+(\w+)/); 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) { // 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 have_itmenu = false; var have_itmanage = false; var update_itmanage = function(n, itm) { if (!have_itmenu) return; var div = document.getElementById('IgnorelistPopout2'); 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'; settings_menu.register(div, thread_menu); b.appendChild(div); PM2('Ignorelist'); PM2('RateThread'); PM2('ForumTools'); have_itmenu = true; if (settings.get('inthread_manage') !== undefined) update_itmanage(undefined, settings.get('inthread_manage')); }); } else if (have_itmenu) { var elm = document.getElementById('Ignorelist2'); 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)); 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)); last_viewed.set(thread_id, p.cur, lpn, lp); }); if (!ftl.perma && thread_id !== undefined) { if (location.hash !== '') scroll_to_hash(); } } if (ftl.settings) { block.watch('ready', function() { var panel = document.getElementsByClassName('PageContentPanel')[0]; var div = document.createElement('div'); div.style.marginTop = '5px'; var tbl = 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>'; div.appendChild(tbl); 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 Forum Enhancements Settings'; cells[5].style.padding = '0px'; var menu = document.createElement('div'); menu.className = 'BGShadow'; menu.style.padding = '10px'; settings_menu.register(menu, full_menus); cells[5].appendChild(menu); cells[3].style.padding = '0px'; menu = document.createElement('div'); menu.className = 'BGShadow'; menu.style.padding = '10px'; manage_menu.register(menu); menu.getElementsByClassName('il_addbtn')[0].className = 'FormButton blue'; cells[3].appendChild(menu); panel.appendChild(div); }); }