var $ = {}; $.id = function(id) { return document.getElementById(id); }; $.cls = function(klass, root) { return (root || document).getElementsByClassName(klass); }; $.tag = function(tag, root) { return (root || document).getElementsByTagName(tag); }; $.extend = function(destination, source) { for (var key in source) { destination[key] = source[key]; } }; $.on = function(n, e, h) { n.addEventListener(e, h, false); }; $.off = function(n, e, h) { n.removeEventListener(e, h, false); }; $.readCookie = function(name) { var i, c, ca, key; key = name + '='; ca = document.cookie.split(';'); for (i = 0; c = ca[i]; ++i) { while (c.charAt(0) == ' ') { c = c.substring(1, c.length); } if (c.indexOf(key) === 0) { return decodeURIComponent(c.substring(key.length, c.length)); } } return null; }; if (!document.documentElement.classList) { $.hasClass = function(el, klass) { return (' ' + el.className + ' ').indexOf(' ' + klass + ' ') != -1; }; $.addClass = function(el, klass) { el.className = (el.className === '') ? klass : el.className + ' ' + klass; }; $.removeClass = function(el, klass) { el.className = (' ' + el.className + ' ').replace(' ' + klass + ' ', ''); }; } else { $.hasClass = function(el, klass) { return el.classList.contains(klass); }; $.addClass = function(el, klass) { el.classList.add(klass); }; $.removeClass = function(el, klass) { el.classList.remove(klass); }; } $.toggleClass = function(el, klass) { if ($.hasClass(el, klass)) { $.removeClass(el, klass); } else { $.addClass(el, klass); } }; var UA = {}; UA.init = function() { document.head = document.head || $.tag('head')[0]; this.hasContextMenu = 'HTMLMenuItemElement' in window; this.hasWebStorage = (function() { var kv = 'catalog'; try { localStorage.setItem(kv, kv); localStorage.removeItem(kv); return true; } catch (e) { return false; } })(); this.hasSessionStorage = (function() { var kv = 'catalog'; try { sessionStorage.setItem(kv, kv); sessionStorage.removeItem(kv); return true; } catch (e) { return false; } })(); this.hasCORS = 'withCredentials' in new XMLHttpRequest(); this.isMobileDevice = /Mobile|Android|Dolfin|Opera Mobi|PlayStation Vita|Nintendo DS/.test(navigator.userAgent); }; UA.dispatchEvent = function(name, detail) { var e = document.createEvent('Event'); e.initEvent(name, false, false); if (detail) { e.detail = detail; } document.dispatchEvent(e); }; var FC = function() { var self = this, options = { orderby: 'alt', large: false, extended: true, imgdel: '//s.4cdn.org/image/filedeleted-res.gif', imgspoiler: '//s.4cdn.org/image/spoiler', nofile: '//s.4cdn.org/image/nofile.png', smallsize: 150, tipdelay: 250, filterColors: [ ['#E0B0FF', '#F2F3F4', '#7DF9FF', '#FFFF00'], ['#FBCEB1', '#FFBF00', '#ADFF2F', '#0047AB'], ['#00A550', '#007FFF', '#AF0A0F', '#B5BD68'] ] }, capcodeMap = { admin: 'Administrator', mod: 'Moderator', developer: 'Developer', manager: 'Manager', founder: 'Founder', verified: 'Verified' }, keybinds = { 83: focusQuickfilter, // S 82: refreshWindow, // R 88: cycleOrder // X }, catalog = {}, basicSettings = [ 'orderby', 'large', 'extended' ], activeTheme = {}, activeStyleGroup, activeStyleSheet, activeFilters = {}, hasTooltip = false, tooltipTimeout = null, pinnedThreads = {}, hiddenThreads = {}, hiddenThreadsCount = 0, filteredThreadsCount = 0, hasThreadWatcher = false, hasDropDownNav = false, hasClassicNav = false, hasAutoHideNav = false, altCaptcha = false, quickFilterPattern = false, hiddenMode = false, $threads, $qfCtrl, $teaserCtrl, $sizeCtrl, $orderCtrl, $filterPalette, ctxCmds; if (window.devicePixelRatio >= 2) { options.imgdel.replace('.', '@2x.'); options.nofile.replace('.', '@2x.'); } UA.init(); loadTheme(); self.init = function() { var extConfig, el, val, fn; FC.hasMobileLayout = checkMobileLayout(); applyTheme(activeTheme, true); $threads = $.id('threads'); $qfCtrl = $.id('qf-ctrl'); $teaserCtrl = $.id('teaser-ctrl'); $sizeCtrl = $.id('size-ctrl'); $orderCtrl = $.id('order-ctrl'); $.on($qfCtrl, 'click', toggleQuickfilter); $.on($.id('filters-clear-hidden'), 'click', toggleHiddenThreads); $.on($.id('filters-clear-hidden-bottom'), 'click', toggleHiddenThreads); $.on($.id('qf-clear'), 'click', toggleQuickfilter); $.on($.id('settingsWindowLink'), 'click', showThemeEditor); $.on($.id('settingsWindowLinkBot'), 'click', showThemeEditor); $.on($.id('settingsWindowLinkMobile'), 'click', showThemeEditor); $.on($.id('filters-ctrl'), 'click', showFilters); $.on($teaserCtrl, 'change', onTeaserChange); $.on($sizeCtrl, 'change', onSizeChange); $.on($orderCtrl, 'change', onOrderChange); $.on($threads, 'mouseover', onThreadMouseOver); $.on($threads, 'mouseout', onThreadMouseOut); $.on($.id('togglePostFormLinkMobile'), 'click', togglePostFormMobile); $.on(document, 'click', onClick); loadSettings(); bindGlobalShortcuts(); initGlobalMessage(); if (UA.hasContextMenu) { buildContextMenu(); } window.Config = {}; if (UA.hasWebStorage) { if (extConfig = localStorage.getItem('4chan-settings')) { extConfig = JSON.parse(extConfig); window.Config = extConfig; if (!extConfig.disableAll) { CustomMenu.initCtrl(extConfig.dropDownNav, extConfig.classicNav); /* if (location.host === 'boards.4channel.org' && extConfig.showNWSBoards) { CustomMenu.showNWSBoards(); } */ if (extConfig.filter) { ThreadWatcher.hasFilters = true; } if (extConfig.threadWatcher) { hasThreadWatcher = true; ThreadWatcher.init(); } if (extConfig.customMenu) { CustomMenu.apply(extConfig.customMenuList); } if (extConfig.dropDownNav !== false && !FC.hasMobileLayout) { hasDropDownNav = true; hasClassicNav = extConfig.classicNav; hasAutoHideNav = extConfig.autoHideNav; showDropDownNav(); } altCaptcha = extConfig.altCaptcha; } } else if (UA.isMobileDevice && !FC.hasMobileLayout) { hasDropDownNav = true; showDropDownNav(); } else { CustomMenu.initCtrl(false, false); } if (window.css_event && activeStyleSheet === '_special') { fn = window['fc_' + window.css_event + '_init']; fn && fn(); } } if (el = document.forms.post.flag) { if ((val = $.readCookie('4chan_flag')) && (el = el.querySelector('option[value="' + val + '"]'))) { el.setAttribute('selected', 'selected'); } } setOrder(options.orderby, true); setLarge(options.large, true); setExtended(options.extended, true); UA.dispatchEvent('4chanMainInit'); }; function showDropDownNav() { var el, top, bottom; top = $.id('boardNavDesktop'); bottom = $.id('boardNavDesktopFoot'); if (hasClassicNav) { el = document.createElement('div'); el.className = 'pageJump'; el.innerHTML = '' + 'Settings' + 'Home'; top.appendChild(el); $.id('settingsWindowLinkClassic') .addEventListener('click', showThemeEditor, false); $.addClass(top, 'persistentNav'); } else { top.style.display = 'none'; $.removeClass($.id('boardNavMobile'), 'mobile'); } if (hasAutoHideNav) { StickyNav.init(hasClassicNav); } bottom.style.display = 'none'; $.addClass(document.body, 'hasDropDownNav'); } function hideDropDownNav() { var el, top, bottom; top = $.id('boardNavDesktop'); bottom = $.id('boardNavDesktopFoot'); if (hasClassicNav) { if (el = $.cls('pageJump', top)[0]) { $.id('settingsWindowLinkClassic') .removeEventListener('click', showThemeEditor, false); top.removeChild(el); } $.removeClass(top, 'persistentNav'); } else { top.style.display = ''; $.addClass($.id('boardNavMobile'), 'mobile'); } if (hasAutoHideNav) { StickyNav.destroy(hasClassicNav); } bottom.style.display = ''; $.removeClass(document.body, 'hasDropDownNav'); } self.loadCatalog = function(c) { var query; catalog = c; $.addClass(document.body, activeStyleSheet.toLowerCase().replace(/ /g, '_')); initStyleSwitcher(); loadFilters(); loadStorage(); if (UA.hasSessionStorage && !location.hash && (query = sessionStorage.getItem('4chan-catalog-search'))) { if (catalog.slug != sessionStorage.getItem('4chan-catalog-search-board')) { sessionStorage.removeItem('4chan-catalog-search'); sessionStorage.removeItem('4chan-catalog-search-board'); query = null; } } else if (location.hash && (query = location.hash.match(/#s=(.+)/))) { query = decodeURIComponent(query[1].replace(/\+/g, ' ')); } if (query) { toggleQuickfilter(); $.id('qf-box').value = query; applyQuickfilter(); } else { buildThreads(); } }; function initGlobalMessage() { var msg, btn, thisTs, oldTs; if (!UA.hasWebStorage || FC.hasMobileLayout) { return; } if ((msg = $.id('globalMessage')) && msg.textContent) { msg.nextElementSibling.style.clear = 'both'; btn = document.createElement('span'); btn.id = 'toggleMsgBtn'; btn.setAttribute('data-cmd', 'toggleMsg'); btn.title = 'Toggle announcement'; oldTs = localStorage.getItem('4chan-global-msg'); thisTs = msg.getAttribute('data-utc'); if (oldTs && thisTs <= oldTs) { msg.style.display = 'none'; btn.style.opacity = '0.5'; btn.className = 'expandIcon'; } else { btn.className = 'collapseIcon'; } $.on(btn, 'click', toggleGlobalCatalogMessage); msg.parentNode.insertBefore(btn, msg); } } function toggleGlobalCatalogMessage() { var msg, btn; msg = $.id('globalMessage'); btn = $.id('toggleMsgBtn'); if (msg.style.display == 'none') { msg.style.display = ''; btn.className = 'collapseIcon'; btn.style.opacity = '1'; localStorage.removeItem('4chan-global-msg'); } else { msg.style.display = 'none'; btn.className = 'expandIcon'; btn.innerHTML = 'View Important Announcement'; btn.style.opacity = '0.5'; localStorage.setItem('4chan-global-msg', msg.getAttribute('data-utc')); } //StorageSync.sync('4chan-global-msg'); } function togglePostFormMobile() { var el = document.getElementById('postForm'); if (el.style.display == 'table') { el.style.display = ''; this.textContent = 'Start a New Thread'; } else { el.style.display = 'table'; this.textContent = 'Close Post Form'; window.initRecaptcha(); window.initTCaptcha(); } } function getRegexSpecials() { var specials = ['/', '.', '*', '+', '?', '(', ')', '[', ']', '{', '}', '\\' ]; return new RegExp('(\\' + specials.join('|\\') + ')', 'g'); } function getThreadPage(tid) { return (0 | (catalog.threads[tid].b / catalog.pagesize)) + 1; } function initStyleSwitcher() { var i, selector, nodes, ss; selector = $.id('styleSelector'); nodes = selector.children; for (i = 0; ss = nodes[i]; ++i) { if (ss.value == activeStyleSheet) { selector.selectedIndex = i; } } $.on(selector, 'change', onStyleSheetChange); } function onStyleSheetChange() { var expires; if (this.value !== '_special') { expires = new Date(); expires.setTime(expires.getTime() + 31536000000); document.cookie = activeStyleGroup + '=' + this.value + '; expires=' + expires.toGMTString() + '; path=/; domain=' + $L.d(catalog.slug); if (window.css_event) { fn = window['fc_' + window.css_event + '_cleanup']; localStorage.setItem('4chan_stop_css_event', `${window.css_event}-${window.css_event_v}`); } } else if (window.css_event) { fn = window['fc_' + window.css_event + '_init']; localStorage.removeItem('4chan_stop_css_event'); } //StorageSync.sync('4chan_stop_css_event'); refreshWindow(); } function refreshWindow(e) { if (e && e.shiftKey) { return; } location.href = location.href; } function debounce(delay, fn) { var timeout; return function() { var args = arguments, context = this; clearTimeout(timeout); timeout = setTimeout(function () { fn.apply(context, args); }, delay); }; } function focusQuickfilter() { if ($.hasClass($qfCtrl, 'active')) { clearQuickfilter(true); } else { toggleQuickfilter(); } } function toggleQuickfilter() { var qfBox, qfcnt = $.id('qf-cnt'); if ($.hasClass($qfCtrl, 'active')) { clearQuickfilter(); qfcnt.style.display = 'none'; $.removeClass($qfCtrl, 'active'); } else { qfcnt.style.display = 'inline'; qfBox = $.id('qf-box'); if (!qfcnt.hasAttribute('data-built')) { qfcnt.setAttribute('data-built', '1'); $.on(qfBox, 'keyup', debounce(250, applyQuickfilter)); $.on(qfBox, 'keydown', function(e) { if (e.keyCode == '27') { toggleQuickfilter(); } }); } qfBox.focus(); qfBox.value = ''; $.addClass($qfCtrl, 'active'); } } function applyQuickfilter() { var regexEscape, qfstr; if ((qfstr = $.id('qf-box').value) !== '') { if (UA.hasSessionStorage) { sessionStorage.setItem('4chan-catalog-search', qfstr); sessionStorage.setItem('4chan-catalog-search-board', catalog.slug); } regexEscape = getRegexSpecials(); $.id('search-term').textContent = $.id('search-term-bottom').textContent = qfstr; $.id('search-label').style.display = $.id('search-label-bottom').style.display = 'inline'; qfstr = qfstr.replace(regexEscape, '\\$1'); quickFilterPattern = new RegExp(qfstr, 'i'); buildThreads(); } else { clearQuickfilter(); } } function clearQuickfilter(focus) { var qf = $.id('qf-box'); $.id('search-label').style.display = $.id('search-label-bottom').style.display = 'none'; if (focus) { qf.value = ''; qf.focus(); } else { if (UA.hasSessionStorage) { sessionStorage.removeItem('4chan-catalog-search'); } quickFilterPattern = false; buildThreads(); } } function buildContextMenu() { ctxCmds = { pin: toggleThreadPin, hide: toggleThreadHide, report: reportThread }; $.id('ctxmenu-main').innerHTML = ''; $.id('ctxmenu-thread').innerHTML = '' + '' + ''; $.on($.id('ctxmenu-main'), 'click', clearPinnedThreads); $.on($.id('ctxmenu-thread'), 'click', onThreadContextClick); } function bindGlobalShortcuts() { var el, tid; if (UA.hasWebStorage) { $.on($threads, 'mousedown', function(e) { el = e.target; if (el.className.indexOf('thumb') != -1) { tid = el.getAttribute('data-id'); if (e.which == 3) { $threads.setAttribute('contextmenu', 'ctxmenu-thread'); $.id('ctxmenu-thread').target = tid; } else if (e.which == 1 && e.altKey) { toggleThreadPin(tid); return false; } else if (e.which == 1 && e.shiftKey) { toggleThreadHide(tid); return false; } } else if (e.which == 3) { $threads.setAttribute('contextmenu', 'ctxmenu-main'); } }); } if (!activeTheme.nobinds) { $.on(document, 'keyup', processKeybind); } } function toggleThreadPin(tid) { if (pinnedThreads[tid] >= 0) { delete pinnedThreads[tid]; } else { pinnedThreads[tid] = catalog.threads[tid].r || 0; } localStorage.setItem('4chan-pin-' + catalog.slug, JSON.stringify(pinnedThreads)); buildThreads(); } function toggleThreadHide(tid) { if (hiddenMode) { delete hiddenThreads[tid]; --hiddenThreadsCount; } else { hiddenThreads[tid] = true; ++hiddenThreadsCount; } localStorage.setItem('4chan-hide-t-' + catalog.slug, JSON.stringify(hiddenThreads)); $.id('thread-' + tid).style.display = 'none'; setHiddenCount(hiddenThreadsCount); if (hiddenThreadsCount === 0) { setHiddenMode(false); } } function setHiddenMode(state) { hiddenMode = state; $.id('filters-clear-hidden').textContent = $.id('filters-clear-hidden-bottom').textContent = state ? 'Back' : 'Show'; buildThreads(); } function setProcessedCount(type, num) { var label = type + '-label', count = type + '-count'; if (num > 0) { $.id(count).textContent = $.id(count + '-bottom').textContent = num; $.id(label).style.display = $.id(label + '-bottom').style.display = 'inline'; } else { $.id(label).style.display = $.id(label + '-bottom').style.display = 'none'; } } function setHiddenCount(num) { setProcessedCount('hidden', num); } function setFilteredCount(num) { setProcessedCount('filtered', num); } function reportThread(tid) { var height, altc; if (window.passEnabled || !window.grecaptcha) { height = 175; } else if (altCaptcha) { height = 320; altc = '&altc=1'; } else { height = 510; altc = ''; } window.open( 'https://sys.' + $L.d(catalog.slug) + '/' + catalog.slug + '/imgboard.php?mode=report&no=' + tid + altc, Date.now(), 'toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,width=380,height=' + height ); } function onThreadContextClick(e) { var cmd = e.target.getAttribute('data-cmd'); ctxCmds[cmd]($.id('ctxmenu-thread').target); } function processKeybind(e) { var el = e.target; if (el.nodeName == 'TEXTAREA' || el.nodeName == 'INPUT') { return; } if (keybinds[e.keyCode]) { keybinds[e.keyCode](e); } } function toggleHiddenThreads(e) { var el; e.preventDefault(); if (hiddenThreadsCount > 0) { if ((el = $.id('filters-clear-hidden')).textContent == 'Show') { setHiddenMode(true); } else { setHiddenMode(false); } } } function clearPinnedThreads() { pinnedThreads = {}; localStorage.removeItem('4chan-pin-' + catalog.slug); buildThreads(); return false; } function onClick(e) { var t = e.target, tid; if ((t = e.target) == document) { return; } if (tid = t.getAttribute('data-watch')) { ThreadWatcher.toggle( tid, catalog.slug, catalog.threads[tid].sub, catalog.threads[tid].teaser, catalog.threads[tid].lr.id ); } else if (tid = t.getAttribute('data-hide')) { e.preventDefault(); toggleThreadHide(tid); } else if (tid = t.getAttribute('data-pin')) { e.preventDefault(); toggleThreadPin(tid); } else if (tid = t.getAttribute('data-report')) { e.preventDefault(); reportThread(tid); } else if (tid = t.getAttribute('data-post-menu')) { e.preventDefault(); PostMenu.open(t, tid, hasThreadWatcher, hiddenThreads[tid], pinnedThreads[tid]); } else if (t.hasAttribute('data-cm-edit')) { e.preventDefault(); CustomMenu.showEditor(true); } else if (t.id == 'backdrop') { if (!panelHidden($.id('filters'))) { if (!panelHidden($.id('filters-protip'))) { closeFiltersHelp(); } else { closeFilters(); } } else if (!panelHidden($.id('theme'))) { closeThemeEditor(); } } else if (e.target.id == 'filter-palette') { closeFilterPalette(); } } function buildFilterPalette() { var i, j, table, palette, rows, cols, tr, td, foot; $filterPalette = $.id('filter-palette'); table = $.id('filter-color-table'); palette = $.tag('tbody', table)[0]; rows = options.filterColors.length; if (rows > 0) { cols = options.filterColors[0].length; foot = $.tag('tfoot', table)[0]; for (i = foot.children.length - 1; i >= 0; i--) { foot.children[i].firstElementChild.setAttribute('colspan', cols); } } for (i = 0; i < rows; ++i) { tr = document.createElement('tr'); for (j = 0; j < cols; ++j) { td = document.createElement('td'); td.innerHTML = ''; $.on(td.firstElementChild, 'click', selectFilterColor); tr.appendChild(td); } palette.appendChild(tr); } } function showFilterPalette(el) { var picker, pos = el.getBoundingClientRect(); if (!$filterPalette) { buildFilterPalette(); } $.removeClass($filterPalette, 'hidden'); $filterPalette.setAttribute('data-target', el.id.split('-')[2]); picker = $filterPalette.firstElementChild; picker.style.cssText = 'top:' + pos.top + 'px;left:' + (pos.left - picker.clientWidth - 10) + 'px;'; } function showFiltersHelp() { var el = $.id('filters-protip'); el.style.top = window.pageYOffset + 50 + 'px'; $.removeClass(el, 'hidden'); } function closeFiltersHelp() { $.addClass($.id('filters-protip'), 'hidden'); } function onFiltersClick(e) { var t = e.target; if (t.id == 'filters-close') closeFilters(); else if (t.id == 'filters-add') addEmptyFilter(); else if (t.id == 'filters-save') { saveFilters(); closeFilters(); } else if (t.hasAttribute('data-active')) toggleFilter(t, 'active'); else if (t.hasAttribute('data-hide')) toggleFilter(t, 'hide', 'top'); else if (t.hasAttribute('data-top')) toggleFilter(t, 'top', 'hide'); else if ($.hasClass(t, 'filter-color')) showFilterPalette(t); else if (t.hasAttribute('data-target')) deleteFilter(t); else if (t.hasAttribute('data-up')) moveFilterUp(t); } function moveFilterUp(el) { var tr, prev; tr = el.parentNode.parentNode; prev = tr.previousElementSibling; if (prev) { tr.parentNode.insertBefore(tr, prev); } } function onFiltersSearch(e) { var i, el, nodes, cnt, str; if (e && (e.keyCode == 27)) { this.value = ''; } str = this.value.toLowerCase(); nodes = document.getElementsByClassName('filter-pattern'); cnt = document.getElementById('filter-list'); cnt.style.display = 'none'; for (i = 0; el = nodes[i]; ++i) { if (el.value.toLowerCase().indexOf(str) === -1) { el.parentNode.parentNode.style.display = 'none'; } else { el.parentNode.parentNode.style.display = ''; } } cnt.style.display = ''; } function showFilters() { var i, filtersPanel, rawFilters, filterList, filterId, el; filtersPanel = $.id('filters'); if (!filtersPanel) { filtersPanel = FC.panelHTML.build('filters', 'panel hidden'); FC.panelHTML.build('filters-protip', 'panel hidden'); FC.panelHTML.build('filter-palette', 'hidden'); } if (!filtersPanel.hasAttribute('data-built')) { $.on(filtersPanel, 'click', onFiltersClick); $.on($.id('filter-palette-close'), 'click', closeFilterPalette); $.on($.id('filter-palette-clear'), 'click', clearFilterColor); $.on($.id('filters-help-open'), 'click', showFiltersHelp); $.on($.id('filters-help-close'), 'click', closeFiltersHelp); $.on($.id('filter-rgb'), 'keyup', filterSetCustomColor); $.on($.id('filter-rgb-ok'), 'click', selectFilterColor); $.on($.id('filters-search'), 'keyup', onFiltersSearch); filtersPanel.setAttribute('data-built', '1'); } else { $.id('filters-search').value = ''; } rawFilters = localStorage.getItem('catalog-filters'); filterId = 0; if (rawFilters) { filterList = $.id('filter-list'); rawFilters = JSON.parse(rawFilters); for (i in rawFilters) { filterList.appendChild(buildFilter(rawFilters[i], filterId)); ++filterId; } updateFilterHitCount(); } filtersPanel.style.top = window.pageYOffset + 60 + 'px'; $.removeClass(filtersPanel, 'hidden'); if (el = $.cls('filter-active', filtersPanel)[0]) { el.focus(); } toggleBackdrop(); } function closeFilters() { var i, filterList, nodes; $.id('filters-msg').style.display = 'none'; $.addClass($.id('filters'), 'hidden'); filterList = $.id('filter-list'); nodes = $.tag('tr', filterList); for (i = nodes.length - 1; i >= 0; i--) { filterList.removeChild(nodes[i]); } closeFilterPalette(); toggleBackdrop(); } function closeFilterPalette() { if ($filterPalette && !$.hasClass($filterPalette, 'hidden')) { $.addClass($filterPalette, 'hidden'); } } // Loads patterns from the localStorage and builds regexps function loadFilters() { if (!UA.hasWebStorage) return; activeFilters = {}; var rawFilters = localStorage.getItem('catalog-filters'); if (!rawFilters) return; rawFilters = JSON.parse(rawFilters); var rf, fid, v, w, wordcount, wordSepS, wordSepE, regexType = /^\/(.*)\/(i?)$/, regexOrNorm = /\s*\|+\s*/g, regexWc = /\\\*/g, replWc = '[^\\s]*', regexEscape = getRegexSpecials(), match, inner, words, rawPattern, pattern, orOp, orCluster, type; wordSepS = '(?=.*\\b'; wordSepE = '\\b)'; try { for (fid in rawFilters) { rf = rawFilters[fid]; if (rf.active && rf.pattern !== '') { if (rf.boards && rf.boards.split(' ').indexOf(catalog.slug) == -1) { continue; } rawPattern = rf.pattern; if (rawPattern.charAt(0) == '#') { type = (rawPattern.charAt(1) == '#') ? 2 : 1; pattern = new RegExp(rawPattern.slice(type).replace(regexEscape, '\\$1')); } else { type = 0; if (match = rawPattern.match(regexType)) { pattern = new RegExp(match[1], match[2]); } else if (rawPattern.charAt(0) == '"' && rawPattern.charAt(rawPattern.length - 1) == '"') { pattern = new RegExp(rawPattern.slice(1, -1).replace(regexEscape, '\\$1')); } else { words = rawPattern.replace(regexOrNorm, '|').split(' '); pattern = ''; wordcount = words.length; for (w = 0; w < wordcount; ++w) { if (words[w].indexOf('|') != -1) { orOp = words[w].split('|'); orCluster = []; for (v = orOp.length - 1; v >= 0; v--) { if (orOp[v] !== '') { orCluster.push(orOp[v].replace(regexEscape, '\\$1')); } } inner = orCluster.join('|').replace(regexWc, replWc); pattern += wordSepS + '(' + inner + ')' + wordSepE; } else { inner = words[w].replace(regexEscape, '\\$1').replace(regexWc, replWc); pattern += wordSepS + inner + wordSepE; } } pattern = new RegExp('^' + pattern, 'i'); } } //console.log('Resulting regex: ' + pattern); activeFilters[fid] = { type: type, pattern: pattern, boards: rf.boards, fid: fid, hidden: rf.hidden, color: rf.color, top: rf.top, hits: 0 }; } } } catch (err) { alert('There was an error processing one of the filters: ' + err + ' in: ' + rf.pattern); } } function saveFilters() { var i, j, f, rawFilters, filterList, msg, rows, color; rawFilters = {}; filterList = $.id('filter-list'); rows = filterList.children; for (i = 0; j = rows[i]; ++i) { f = { active: $.cls('filter-active', j)[0].checked ? 1 : 0, pattern: $.cls('filter-pattern', j)[0].value, boards: $.cls('filter-boards', j)[0].value, hidden: $.cls('filter-hide', j)[0].checked ? 1 : 0, top: $.cls('filter-top', j)[0].checked ? 1 : 0 }; color = $.cls('filter-color', j)[0]; if (!color.hasAttribute('data-nocolor')) { f.color = color.style.backgroundColor; } rawFilters[i] = f; } if (rawFilters[0]) { localStorage.setItem('catalog-filters', JSON.stringify(rawFilters)); } else { localStorage.removeItem('catalog-filters'); } msg = $.id('filters-msg'); msg.innerHTML = 'Done'; msg.className = 'msg-ok'; msg.style.display = 'inline'; setTimeout(function() { msg.style.display = 'none'; }, 2000); loadFilters(); buildThreads(); updateFilterHitCount(); } function filterSetCustomColor() { var filterRgbOk; filterRgbOk = $.id('filter-rgb-ok'); filterRgbOk.style.backgroundColor = this.value; } function buildFilter(filter, id) { var td, tr, span, input; tr = document.createElement('tr'); tr.id = 'filter-' + id; // Move up td = document.createElement('td'); span = document.createElement('span'); span.setAttribute('data-up', id); span.className = 'pointer'; span.innerHTML = '↑'; td.appendChild(span); tr.appendChild(td); // On td = document.createElement('td'); input = document.createElement('input'); input.type = 'checkbox'; input.checked = !!filter.active; input.className = 'filter-active'; td.appendChild(input); tr.appendChild(td); // Pattern td = document.createElement('td'); input = document.createElement('input'); input.type = 'text'; input.value = filter.pattern; input.className = 'filter-pattern'; td.appendChild(input); tr.appendChild(td); // Boards td = document.createElement('td'); input = document.createElement('input'); input.type = 'text'; input.value = filter.boards; input.className = 'filter-boards'; td.appendChild(input); tr.appendChild(td); // Color td = document.createElement('td'); span = document.createElement('span'); span.id = 'filter-color-' + id; span.title = 'Change Color'; span.className = 'button clickbox filter-color'; if (!filter.color) { span.setAttribute('data-nocolor', '1'); span.innerHTML = '∕'; } else { span.style.background = filter.color; } td.appendChild(span); tr.appendChild(td); // Hide td = document.createElement('td'); input = document.createElement('input'); input.type = 'checkbox'; input.checked = !!filter.hidden; input.className = 'filter-hide'; td.appendChild(input); tr.appendChild(td); // Top td = document.createElement('td'); input = document.createElement('input'); input.type = 'checkbox'; input.checked = !!filter.top; input.className = 'filter-top'; td.appendChild(input); tr.appendChild(td); // Del td = document.createElement('td'); span = document.createElement('span'); span.setAttribute('data-target', id); span.className = 'pointer'; span.innerHTML = '×'; td.appendChild(span); tr.appendChild(td); // Match count td = document.createElement('td'); td.id = 'fhc-' + id; td.className = 'filter-hits'; tr.appendChild(td); return tr; } function selectFilterColor(clear) { var target = $.id('filter-color-' + $filterPalette.getAttribute('data-target')); if (clear === true) { target.setAttribute('data-nocolor', '1'); target.innerHTML = '∕'; target.style.background = ''; } else { target.removeAttribute('data-nocolor'); target.innerHTML = ''; target.style.background = this.style.backgroundColor; } closeFilterPalette(); } function clearFilterColor() { selectFilterColor(true); } function addEmptyFilter() { var filter = { active: 1, pattern: '', boards: '', color: '', hidden: 0, top: 0, hits: 0 }; $.id('filter-list').appendChild(buildFilter(filter, getNextFilterId())); } function getNextFilterId() { var i, j, max, rows = $.id('filter-list').children; if (!rows.length) { return 0; } else { max = 0; for (i = 0; j = rows[i]; ++i) { j = +j.id.slice(7); if (j > max) { max = j; } } return max + 1; } } function deleteFilter(t) { var el = $.id('filter-' + t.getAttribute('data-target')); el.parentNode.removeChild(el); } function toggleFilter(el, type, xor) { var attr = 'data-' + type, xorEle; if (el.getAttribute(attr) == '0') { el.setAttribute(attr, '1'); $.addClass(el, 'active'); el.innerHTML = '✔'; if (xor) { xorEle = $.cls('filter-' + xor, el.parentNode.parentNode)[0]; xorEle.setAttribute('data-' + xor, '0'); $.removeClass(xorEle, 'active'); xorEle.innerHTML = ''; } } else { el.setAttribute(attr, '0'); $.removeClass(el, 'active'); el.innerHTML = ''; } } function updateFilterHitCount() { var i, j, rows = $.id('filter-list').children; for (i = 0; j = rows[i]; ++i) { $.id('fhc-' + j.id.slice(7)) .innerHTML = activeFilters[i] ? 'x' + activeFilters[i].hits : ''; } } function panelHidden(el) { return el && $.hasClass(el, 'hidden'); } function showThemeEditor() { var themePanel, el, theme; if (!UA.hasWebStorage) { alert("Your browser doesn't support Local Storage"); return; } themePanel = $.id('theme'); if (!themePanel) { themePanel = FC.panelHTML.build('theme', 'panel hidden'); } theme = localStorage.getItem('catalog-theme'); theme = theme ? JSON.parse(theme) : {}; $.id('theme-nobinds').checked = !!theme.nobinds; $.id('theme-nospoiler').checked = !!theme.nospoiler; $.id('theme-newtab').checked = !!theme.newtab; $.id('theme-tw').checked = hasThreadWatcher; $.id('theme-ddn').checked = hasDropDownNav; if (theme.css) { $.id('theme-css').value = theme.css; } $.on($.id('theme-save'), 'click', saveTheme); $.on($.id('theme-close'), 'click', closeThemeEditor); $.id('theme-msg').style.display = 'none'; themePanel.style.top = window.pageYOffset + 60 + 'px'; $.removeClass(themePanel, 'hidden'); if (el = $.tag('input', themePanel)[0]) { el.focus(); } toggleBackdrop(); document.dispatchEvent(new CustomEvent('4chanCatalogThemeEditorReady')); } function closeThemeEditor() { $.off($.id('theme-save'), 'click', saveTheme); $.off($.id('theme-close'), 'click', closeThemeEditor); $.addClass($.id('theme'), 'hidden'); toggleBackdrop(); } function toggleBackdrop() { $.toggleClass($.id('backdrop'), 'hidden'); } function loadTheme() { var customTheme; if (UA.hasWebStorage && (customTheme = localStorage.getItem('catalog-theme'))) { activeTheme = JSON.parse(customTheme); } } function applyTheme(customTheme, nocss) { if (customTheme.nobinds) { if (activeTheme.nobinds != customTheme.nobinds) { $.off(document, 'keyup', processKeybind); } } else { if (activeTheme.nobinds != customTheme.nobinds) { $.on(document, 'keyup', processKeybind); } } if (customTheme.nospoiler) { $.addClass(document.body, 'reveal-img-spoilers'); } else { $.removeClass(document.body, 'reveal-img-spoilers'); } if (!nocss) { self.applyCSS(customTheme); } document.dispatchEvent(new CustomEvent('4chanCatalogThemeApplied')); } self.applyCSS = function(customTheme, style_group, css_version) { var style, ss; if (!customTheme) { customTheme = activeTheme; } // Preferred stylesheet if (style_group !== undefined) { if (!(ss = $.readCookie(style_group))) { ss = style_group == 'nws_style' ? 'Yotsuba New' : 'Yotsuba B New'; } activeStyleGroup = style_group; if (window.css_event && localStorage.getItem('4chan_stop_css_event') !== `${window.css_event}-${window.css_event_v}`) { activeStyleSheet = '_special' ss = window.css_event; } else { activeStyleSheet = ss; } style = document.createElement('link'); style.type = 'text/css'; style.id = 'base-css'; style.rel = 'stylesheet'; style.setAttribute('href', '//s.4cdn.org/css/catalog_' + ss.toLowerCase().replace(/ /g, '_') + '.' + css_version + '.css'); document.head.insertBefore(style, $.id('mobile-css')); } // Custom CSS if (style = $.id('custom-css')) { document.head.removeChild(style); } if (customTheme.css) { style = document.createElement('style'); style.type = 'text/css'; style.id = 'custom-css'; if (style.styleSheet) { style.styleSheet.cssText = customTheme.css; } else { style.innerHTML = customTheme.css; } document.head.appendChild(style); } }; // Applies and saves the theme to localStorage function saveTheme() { var i, css, tw, ddn, extConfig, customTheme = {}; if ($.id('theme-nobinds').checked) { customTheme.nobinds = true; } if ($.id('theme-nospoiler').checked) { customTheme.nospoiler = true; } if ($.id('theme-newtab').checked) { customTheme.newtab = true; } tw = $.id('theme-tw').checked; ddn = $.id('theme-ddn').checked; if (extConfig = localStorage.getItem('4chan-settings')) { extConfig = JSON.parse(extConfig); } else { extConfig = {}; } if (tw != hasThreadWatcher) { if (tw) { ThreadWatcher.init(); extConfig.disableAll = false; } else { ThreadWatcher.unInit(); } } if (ddn != hasDropDownNav) { if (ddn) { showDropDownNav(); extConfig.disableAll = false; } else { hideDropDownNav(); } } extConfig.threadWatcher = tw; extConfig.dropDownNav = ddn; localStorage.setItem('4chan-settings', JSON.stringify(extConfig)); //StorageSync.sync('4chan-settings'); hasThreadWatcher = tw; hasDropDownNav = ddn; if ((css = $.id('theme-css').value) !== '') { customTheme.css = css; } applyTheme(customTheme); localStorage.removeItem('catalog-theme'); for (i in customTheme) { localStorage.setItem('catalog-theme', JSON.stringify(customTheme)); break; } //StorageSync.sync('catalog-theme'); activeTheme = customTheme; buildThreads(); closeThemeEditor(); } function loadThreadList(key) { var i, threads, mod = false, ft = 0; if (threads = localStorage.getItem(key)) { ft = +Object.keys(catalog.threads).pop(); threads = JSON.parse(threads); for (i in threads) { if (!catalog.threads[i] && i < ft) { delete threads[i]; mod = true; } } for (i in threads) { if (mod) { localStorage.setItem(key, JSON.stringify(threads)); } return threads; } localStorage.removeItem(key); } return {}; } function loadStorage() { if (UA.hasWebStorage) { hiddenThreads = loadThreadList('4chan-hide-t-' + catalog.slug); pinnedThreads = loadThreadList('4chan-pin-' + catalog.slug); } } function loadSettings() { var settings; if (UA.hasWebStorage && (settings = localStorage.getItem('catalog-settings'))) { $.extend(options, JSON.parse(settings)); } } function saveSettings() { var i, key, settings; if (!UA.hasWebStorage) { return; } settings = {}; for (i = basicSettings.length - 1; i >= 0; i--) { key = basicSettings[i]; settings[key] = options[key]; } localStorage.setItem('catalog-settings', JSON.stringify(settings)); //StorageSync.sync('catalog-settings'); } function setExtended(mode, init) { var cls = ''; if (mode) { $teaserCtrl.selectedIndex = 1; cls = 'extended-'; options.extended = true; } else { $teaserCtrl.selectedIndex = 0; options.extended = false; } if (options.large) { cls += 'large'; } else { cls += 'small'; } $threads.className = cls; if (!init) { saveSettings(); } } function setLarge(mode, init) { var cls = options.extended ? 'extended-' : ''; if (mode) { $sizeCtrl.selectedIndex = 1; cls += 'large'; options.large = true; } else { $sizeCtrl.selectedIndex = 0; cls += 'small'; options.large = false; } $threads.className = cls; if (!init) { saveSettings(); buildThreads(); } } function setOrder(order, init) { var o = { alt: 0, absdate: 1, date: 2, r: 3 }; if (o[order] !== undefined) { $orderCtrl.selectedIndex = o[order]; options.orderby = order; } else { $orderCtrl.selectedIndex = 0; options.orderby = 'date'; } if (!init) { saveSettings(); buildThreads(); } } function onTeaserChange() { setExtended($teaserCtrl.options[$teaserCtrl.selectedIndex].value == 'on'); } function onOrderChange() { setOrder($orderCtrl.options[$orderCtrl.selectedIndex].value); } function onSizeChange() { setLarge($sizeCtrl.options[$sizeCtrl.selectedIndex].value == 'large'); } function cycleOrder() { if (options.orderby == 'date') { setOrder('alt'); } else if (options.orderby == 'alt') { setOrder('r'); } else if (options.orderby == 'r') { setOrder('absdate'); } else { setOrder('date'); } } function sortThreadList(threadList) { var order = options.orderby; if (order == 'date') { threadList.sort(function(a, b) { if (a.id > b.id) return -1; if (a.id < b.id) return 1; return 0; }); } else if (order == 'absdate' && !catalog.no_lr) { threadList.sort(function(a, b) { a = a.entry.lr.id; b = b.entry.lr.id; if (a > b) return -1; if (a < b) return 1; return 0; }); } else if (order == 'r') { threadList.sort(function(a, b) { var a = a.entry.r || 0, b = b.entry.r || 0; if (a > b) return -1; if (a < b) return 1; return 0; }); } else { // alt threadList.sort(function(a, b) { if (a.entry.b < b.entry.b) return -1; if (a.entry.b > b.entry.b) return 1; return 0; }); } } function getFilteredThreads() { var i, id, entry, hl, onTop, pinned, teaser, tripcode, af, threads, fid, filtered; filtered = 0; threads = []; threadloop: for (id in catalog.threads) { id = +id; entry = catalog.threads[id]; hl = onTop = pinned = false; if (entry.sub) { teaser = '' + entry.sub + ''; if (entry.teaser) { teaser += ': ' + entry.teaser; } } else { teaser = entry.teaser; } if (hiddenMode) { if (!hiddenThreads[id]) { continue; } ++hiddenThreadsCount; } else if(!quickFilterPattern) { if (hiddenThreads[id]) { ++hiddenThreadsCount; continue; } if (pinnedThreads[id] >= 0) { pinned = onTop = true; } else { if (entry.capcode) { tripcode = (entry.trip || '') + '!#' + entry.capcode; } else { tripcode = entry.trip; } for (fid in activeFilters) { af = activeFilters[fid]; if ((af.type == 0 && (af.pattern.test(teaser) || af.pattern.test(entry.file))) || (af.type == 1 && af.pattern.test(tripcode)) || (af.type == 2 && af.pattern.test(entry.author))) { if (af.hidden) { ++filtered; af.hits += 1; continue threadloop; } hl = af; onTop = !!af.top; af.hits += 1; break; } } } } else if (!quickFilterPattern.test(teaser) && !quickFilterPattern.test(entry.file)) { continue; } if (pinnedThreads[id] >= 0) { pinned = onTop = true; } threads.push( { id: id, entry: entry, pinned: pinned, onTop: onTop, hl: hl } ); } filteredThreadsCount = filtered; return threads; } function formatImageThreads(threads) { var i, k, id, entry, item, thread, hl, onTop, pinned, spoiler, rDiff, html, provider, contentUrl, pinhl, newtab, watchKey, teaser, topHtml, stickyHtml, ratio, maxSize, imgWidth, imgHeight, calcSize, capcodeReplies, capcodeReply, capcodeTitle, page; provider = '//boards.' + $L.d(catalog.slug) + '/' + catalog.slug + '/thread/'; contentUrl = 'i.4cdn.org/' + catalog.slug + '/'; calcSize = !options.large; newtab = activeTheme.newtab ? 'target="_blank" ' : ''; if (catalog.custom_spoiler) { spoiler = options.imgspoiler + '-' + catalog.slug + catalog.custom_spoiler + '.png'; } else { spoiler = options.imgspoiler + '.png'; } html = ''; topHtml = ''; stickyHtml = ''; for (i = 0; item = threads[i]; ++i) { id = item.id; entry = item.entry; hl = item.hl; onTop = item.onTop; pinned = item.pinned; if (entry.sub) { teaser = '' + entry.sub + ''; if (entry.teaser) { teaser += ': ' + entry.teaser; } } else { teaser = entry.teaser; } thread = '
'; if (hasThreadWatcher) { watchKey = id + '-' + catalog.slug; thread += '' : 'title="Watch" class="watchIcon">'); } thread += ''; if (entry.sticky || entry.closed || entry.capcodereps) { thread += '
'; if (entry.sticky) { thread += ''; } if (entry.closed) { thread += ''; } if (entry.capcodereps) { capcodeReplies = entry.capcodereps.split(','); for (k = 0; capcodeReply = capcodeReplies[k]; ++k) { if (capcodeTitle = capcodeMap[capcodeReply]) { thread += ''; } } } thread += '
'; } thread += '
'; if (entry.bumplimit) { thread += 'R: ' + entry.r + ''; } else { thread += 'R: ' + entry.r + ''; } if (pinned) { rDiff = entry.r - pinnedThreads[id]; if (rDiff > 0) { thread += ' (+' + rDiff + ')'; pinnedThreads[id] = entry.r; } else { thread += '(+0)'; } } if (entry.i) { if (entry.imagelimit) { thread += ' / I: ' + entry.i + ''; } else { thread += ' / I: ' + entry.i + ''; } } if (onTop && (page = getThreadPage(id)) >= 0) { thread += ' / P: ' + page + ''; } thread += ''; thread += '
'; if (teaser) { thread += '
'; } if (window.partyHats) { thread = '
' + thread + '
'; } else { thread += '
'; } if (entry.sticky) { stickyHtml += thread; } else if (onTop) { topHtml += thread; } else { html += thread; } } topHtml = stickyHtml + topHtml; if (quickFilterPattern && (html === '' && topHtml === '')) { html = '
Nothing Found
'; } else if (topHtml) { html = topHtml + html + '
'; } else { html += '
'; } return html; } function formatTextThreads(threads) { var i, id, entry, item, thread, hl, onTop, pinned, rDiff, html, provider, pinhl, newtab, topHtml, aTag; provider = '//boards.' + $L.d(catalog.slug) + '/' + catalog.slug + '/thread/'; newtab = activeTheme.newtab ? 'target="_blank" ' : ''; html = ''; topHtml = ''; for (i = 0; item = threads[i]; ++i) { id = item.id; entry = item.entry; hl = item.hl; onTop = item.onTop; pinned = item.pinned; if (hl.color) { pinhl = ' class="hl" style="box-shadow: -3px 0 ' + hl.color + '"'; } else if (pinned) { pinhl = ' class="pinned"'; } else { pinhl = ''; } aTag = ''; thread = '' + aTag + '»' + aTag + entry.sub + ''; if (entry.bumplimit) { thread += '' + entry.r + ''; } else { thread += entry.r; } if (pinned) { rDiff = entry.r - pinnedThreads[id]; if (rDiff > 0) { thread += ' (+' + rDiff + ')'; pinnedThreads[id] = entry.r; } else { thread += '(+0)'; } } thread += '' + entry.date + ''; if (onTop) { topHtml += thread; } else { html += thread; } } if (quickFilterPattern && (html === '' && topHtml === '')) { html = '
Nothing Found
'; } else if (topHtml) { html = topHtml + html + '
'; } else { html += '
'; } html = '' + '' + html + '
SubjectRepliesDate
'; return html; } function buildThreads() { var i, tip, fid, threads; if (catalog.count === 0) { return; } if ($threads.hasChildNodes()) { if (tip = document.getElementById('th-tip')) { document.body.removeChild(tip); } $threads.textContent = ''; } hiddenThreadsCount = 0; filteredThreadsCount = 0; for (fid in activeFilters) { activeFilters[fid].hits = 0; } threads = getFilteredThreads(); sortThreadList(threads); if (!window.text_only) { $threads.innerHTML = formatImageThreads(threads); } else { $threads.innerHTML = formatTextThreads(threads); } for (i in pinnedThreads) { localStorage.setItem('4chan-pin-' + catalog.slug, JSON.stringify(pinnedThreads)); break; } setFilteredCount(filteredThreadsCount); setHiddenCount(hiddenThreadsCount); } function onThreadMouseOver(e) { var t = e.target; if ($.hasClass(t, 'thumb') || (window.text_only && $.hasClass(t, 'txt-date'))) { clearTimeout(tooltipTimeout); if (hasTooltip) { hideTooltip(); } tooltipTimeout = setTimeout(showTooltip, options.tipdelay, t); } } function onThreadMouseOut() { clearTimeout(tooltipTimeout); if (hasTooltip) { hideTooltip(); } } function showTooltip(t) { var now, tip, el, rect, docWidth, style, page, tid, thread, top, bottom, docHeight, left; now = Date.now() / 1000; rect = t.getBoundingClientRect(); docWidth = document.documentElement.offsetWidth; tid = t.getAttribute('data-id'); if (!tid) { return; } thread = catalog.threads[tid]; if (page = getThreadPage(tid)) { page = 'Page ' + page + ''; } else { page = ''; } if (thread.sub && !window.text_only) { tip = '' + thread.sub + ''; } else { tip = 'Posted'; } tip += ' by ' + (thread.author || catalog.anon); if (thread.trip) { tip += ' ' + thread.trip + ''; } if (thread.capcode) { tip += ' ## ' + capcodeMap[thread.capcode]; } tip += ' '; if (catalog.flags && thread.country) { tip += '
'; } tip += '' + getDuration(now - thread.date) + ' ago' + page; if ((!options.extended && thread.teaser) || window.text_only) { tip += '

' + thread.teaser + '

'; } if (thread.lr.date) { tip += '
Last reply by ' + thread.lr.author; if (thread.lr.trip) { tip += ' ' + thread.lr.trip + ''; } if (thread.lr.capcode) { tip += ' ## ' + thread.lr.capcode.charAt(0).toUpperCase() + thread.lr.capcode.slice(1); } if (thread.lr.date) { tip += ' ' + getDuration(now - thread.lr.date) + ' ago'; } else { tip += ''; } } el = document.createElement('div'); el.id = 'post-preview'; el.innerHTML = tip; document.body.appendChild(el); if ((docWidth - rect.right) < (0 | (docWidth * 0.3))) { left = rect.left - el.offsetWidth - 5; } else { left = rect.left + rect.width + 5; } docHeight = document.documentElement.clientHeight; bottom = rect.top + el.offsetHeight; if (bottom > docHeight) { top = rect.top - (bottom - docHeight) - 20; } else { top = rect.top; } if (top < 0) { top = 3; } style = el.style; style.left = left + window.pageXOffset + 'px'; style.top = top + window.pageYOffset + 'px'; hasTooltip = true; } function hideTooltip() { document.body.removeChild($.id('post-preview')); hasTooltip = false; } function getDuration(delta, precise) { var count, head, tail; if (delta < 2) { return 'less than a second'; } if (precise && delta < 300) { return (0 | delta) + ' seconds'; } if (delta < 60) { return (0 | delta) + ' seconds'; } if (delta < 3600) { count = 0 | (delta / 60); if (count > 1) { return count + ' minutes'; } else { return 'one minute'; } } if (delta < 86400) { count = 0 | (delta / 3600); if (count > 1) { head = count + ' hours'; } else { head = 'one hour'; } tail = 0 | (delta / 60 - count * 60); if (tail > 1) { head += ' and ' + tail + ' minutes'; } return head; } count = 0 | (delta / 86400); if (count > 1) { head = count + ' days'; } else { head = 'one day'; } tail = 0 | (delta / 3600 - count * 24); if (tail > 1) { head += ' and ' + tail + ' hours'; } return head; } }; var Filter = {}; Filter.init = function() { this.entities = document.createElement('div'); Filter.load(); }; Filter.match = function(post, board) { var i, com, f, filters, hit; hit = false; filters = Filter.activeFilters; for (i = 0; f = filters[i]; ++i) { // boards if (!f.boards[board]) { continue; } // tripcode if (f.type == 0) { if (f.pattern === post.trip) { hit = true; break; } } // name else if (f.type == 1) { if (f.pattern === post.name) { hit = true; break; } } // comment else if (f.type == 2 && post.com) { if (com === undefined) { this.entities.innerHTML = post.com.replace(/
/g, '\n').replace(/[<[^>]+>/g, ''); com = this.entities.textContent; } if (f.pattern.test(com)) { hit = true; break; } } // user id else if (f.type == 4) { if (f.pattern === post.id) { hit = true; break; } } // subject else if (f.type == 5) { if (f.pattern.test(post.sub)) { hit = true; break; } } // filename else if (f.type == 6) { if (f.pattern.test(post.filename)) { hit = true; break; } } } return hit; }; FC.getDocTopOffset = function() { if (window.Config.dropDownNav && !window.Config.autoHideNav) { return $.id( window.Config.classicNav ? 'boardNavDesktop' : 'boardNavMobile' ).offsetHeight; } else { return 0; } }; Filter.load = function() { var i, j, f, rawFilters, rawPattern, fid, regexEscape, regexType, wordSepS, wordSepE, words, inner, regexWildcard, replaceWildcard, boards, pattern, match, tmp; this.activeFilters = []; if (!(rawFilters = localStorage.getItem('4chan-filters'))) { return; } rawFilters = JSON.parse(rawFilters); regexEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '(', ')', '[', ']', '{', '}', '\\', '^', '$' ].join('|\\') + ')', 'g'); regexType = /^\/(.*)\/(i?)$/; wordSepS = '(?=.*\\b'; wordSepE = '\\b)'; regexWildcard = /\\\*/g; replaceWildcard = '[^\\s]*'; try { for (fid = 0; f = rawFilters[fid]; ++fid) { if (f.active && f.pattern !== '') { // Boards if (f.boards) { tmp = f.boards.split(/[^a-z0-9]+/i); boards = {}; for (i = 0; j = tmp[i]; ++i) { boards[j] = true; } } else { boards = false; } rawPattern = f.pattern; // Name, Tripcode or ID, string comparison if (!f.type || f.type == 1 || f.type == 4) { pattern = rawPattern; } // /RegExp/ else if (match = rawPattern.match(regexType)) { pattern = new RegExp(match[1], match[2]); } // "Exact match" else if (rawPattern[0] == '"' && rawPattern[rawPattern.length - 1] == '"') { pattern = new RegExp(rawPattern.slice(1, -1).replace(regexEscape, '\\$1')); } // Full words, AND operator else { words = rawPattern.split(' '); pattern = ''; for (i = 0, j = words.length; i < j; ++i) { inner = words[i] .replace(regexEscape, '\\$1') .replace(regexWildcard, replaceWildcard); pattern += wordSepS + inner + wordSepE; } pattern = new RegExp('^' + pattern, 'im'); } //console.log('Resulting pattern: ' + pattern); this.activeFilters.push({ type: f.type, pattern: pattern, boards: boards, color: f.color, hide: f.hide, auto: f.auto }); } } } catch (e) { alert('There was an error processing one of the filters: ' + e + ' in: ' + rawPattern); } }; /** * Thread watcher */ var ThreadWatcher = { hasFilters: false }; ThreadWatcher.init = function() { var cnt, pos, el; if (this.hasFilters) { Filter.init(); } this.listNode = null; this.charLimit = 45; this.watched = {}; this.blacklisted = {}; this.isRefreshing = false; if (FC.hasMobileLayout) { el = document.createElement('a'); el.href = '#'; el.textContent = 'TW'; el.addEventListener('click', ThreadWatcher.toggleList, false); cnt = $.id('settingsWindowLinkMobile'); cnt.parentNode.insertBefore(el, cnt); cnt.parentNode.insertBefore(document.createTextNode(' '), cnt); } cnt = document.createElement('div'); cnt.id = 'threadWatcher'; cnt.setAttribute('data-trackpos', 'TW-position'); if (FC.hasMobileLayout) { cnt.style.display = 'none'; } else { if (window.Config['TW-position']) { cnt.style.cssText = window.Config['TW-position']; } else { cnt.style.left = '10px'; cnt.style.top = '75px'; } } cnt.innerHTML = '
' + (FC.hasMobileLayout ? ('
') : '') + 'Thread Watcher' + (UA.hasCORS ? ('
') : '
'); this.listNode = document.createElement('ul'); this.listNode.id = 'watchList'; this.load(); this.build(); cnt.appendChild(this.listNode); document.body.appendChild(cnt); cnt.addEventListener('mouseup', this.onClick, false); Draggable.set($.id('twHeader')); window.addEventListener('storage', this.syncStorage, false); if (!FC.hasMobileLayout && this.canAutoRefresh()) { this.refresh(); } }; ThreadWatcher.unInit = function() { var cnt; if (cnt = $.id('threadWatcher')) { cnt.removeEventListener('mouseup', this.onClick, false); Draggable.unset($.id('twHeader')); window.removeEventListener('storage', this.syncStorage, false); document.body.removeChild(cnt); } }; ThreadWatcher.toggleList = function(e) { var el = $.id('threadWatcher'); e && e.preventDefault(); if (ThreadWatcher.canAutoRefresh()) { ThreadWatcher.refresh(); } if (el.style.display == 'none') { el.style.top = (window.pageYOffset + 30) + 'px'; el.style.display = ''; } else { el.style.display = 'none'; } }; ThreadWatcher.syncStorage = function(e) { var key; if (!e.key) { return; } key = e.key.split('-'); if (key[0] == '4chan' && key[1] == 'watch' && e.newValue != e.oldValue) { ThreadWatcher.load(); ThreadWatcher.build(); } }; ThreadWatcher.load = function() { var storage; if (storage = localStorage.getItem('4chan-watch')) { this.watched = JSON.parse(storage); } if (storage = localStorage.getItem('4chan-watch-bl')) { this.blacklisted = JSON.parse(storage); } }; ThreadWatcher.build = function() { var html, tuid, key, cls; html = ''; for (key in this.watched) { tuid = key.split('-'); html += '
  • × (' + this.watched[key][2] + ') '; } else { html += (cls[0] ? ('class="' + cls.join(' ') + '"') : '') + '>'; } } html += '/' + tuid[1] + '/ - ' + this.watched[key][0] + '
  • '; } ThreadWatcher.listNode.innerHTML = html; }; ThreadWatcher.onClick = function(e) { var t = e.target; if (t.hasAttribute('data-id')) { ThreadWatcher.toggle( t.getAttribute('data-id'), t.getAttribute('data-board') ); } else if (t.id == 'twPrune' && !ThreadWatcher.isRefreshing) { ThreadWatcher.refreshWithAutoWatch(); } else if (t.id == 'twClose') { ThreadWatcher.toggleList(); } }; ThreadWatcher.generateLabel = function(sub, com, tid) { var label; if (label = sub) { label = label.slice(0, this.charLimit); } else if (label = com) { label = label.replace(/(?:
    )+/g, ' ') .replace(/<[^>]*?>/g, '').slice(0, this.charLimit); } else { label = 'No.' + tid; } return label; }; ThreadWatcher.toggle = function(tid, board, sub, com, lr) { var key, label, lastReply, icon; key = tid + '-' + board; icon = $.id('leaf-' + tid); if (this.watched[key]) { delete this.watched[key]; if (icon) { icon.className = 'watchIcon'; icon.title = 'Watch'; } } else { label = ThreadWatcher.generateLabel(sub, com, tid); lastReply = lr || tid; this.watched[key] = [ label, lastReply, 0 ]; icon.className = 'unwatchIcon'; icon.title = 'Unwatch'; } this.save(); this.load(); this.build(); }; ThreadWatcher.addRaw = function(post, board) { var key, label; key = post.no + '-' + board; if (this.watched[key]) { return; } label = ThreadWatcher.generateLabel(post.sub, post.com, post.no); this.watched[key] = [ label, 0, 0 ]; }; ThreadWatcher.save = function() { var i; ThreadWatcher.sortByBoard(); localStorage.setItem('4chan-watch', JSON.stringify(ThreadWatcher.watched)); //StorageSync.sync('4chan-watch'); for (i in ThreadWatcher.blacklisted) { localStorage.setItem('4chan-watch-bl', JSON.stringify(ThreadWatcher.blacklisted)); //StorageSync.sync('4chan-watch-bl'); break; } }; ThreadWatcher.sortByBoard = function() { var i, self, key, sorted, keys; self = ThreadWatcher; sorted = {}; keys = []; for (key in self.watched) { keys.push(key); } keys.sort(function(a, b) { a = a.split('-')[1]; b = b.split('-')[1]; if (a < b) { return -1; } if (a > b) { return 1; } return 0; }); for (i = 0; key = keys[i]; ++i) { sorted[key] = self.watched[key]; } self.watched = sorted; }; ThreadWatcher.canAutoRefresh = function() { var time; if (time = localStorage.getItem('4chan-tw-timestamp')) { return Date.now() - (+time) >= 60000; } return false; }; ThreadWatcher.setRefreshTimestamp = function() { localStorage.setItem('4chan-tw-timestamp', Date.now()); //StorageSync.sync('4chan-tw-timestamp'); }; ThreadWatcher.refreshWithAutoWatch = function() { var i, f, count, board, boards, img; if (!this.hasFilters) { this.refresh(); return; } Filter.load(); boards = {}; count = 0; for (i = 0; f = Filter.activeFilters[i]; ++i) { if (!f.auto || !f.boards) { continue; } for (board in f.boards) { if (boards[board]) { continue; } boards[board] = true; ++count; } } if (!count) { this.refresh(); return; } img = $.id('twPrune'); img.className = 'icon rotateIcon'; this.isRefreshing = true; this.fetchCatalogs(boards, count); }; ThreadWatcher.fetchCatalogs = function(boards, count) { var to, board, catalogs, meta; catalogs = {}; meta = { count: count }; to = 0; for (board in boards) { setTimeout(ThreadWatcher.fetchCatalog, to, board, catalogs, meta); to += 200; } }; ThreadWatcher.parseCatalogJSON = function(data) { var catalog; try { catalog = JSON.parse(data); } catch (e) { console.log(e); catalog = []; } return catalog; }; ThreadWatcher.fetchCatalog = function(board, catalogs, meta) { var xhr; xhr = new XMLHttpRequest(); xhr.open('GET', '//a.4cdn.org/' + board + '/catalog.json'); xhr.onload = function() { meta.count--; catalogs[board] = ThreadWatcher.parseCatalogJSON(this.responseText); if (!meta.count) { ThreadWatcher.onCatalogsLoaded(catalogs); } }; xhr.onerror = function() { meta.count--; if (!meta.count) { ThreadWatcher.onCatalogsLoaded(catalogs); } }; xhr.send(null); }; ThreadWatcher.onCatalogsLoaded = function(catalogs) { var i, j, board, page, pages, threads, thread, key, blacklisted; $.id('twPrune').className = 'icon rotateIcon'; this.isRefreshing = false; blacklisted = {}; for (board in catalogs) { pages = catalogs[board]; for (i = 0; page = pages[i]; ++i) { threads = page.threads; for (j = 0; thread = threads[j]; ++j) { key = thread.no + '-' + board; if (this.blacklisted[key]) { blacklisted[key] = 1; continue; } if (Filter.match(thread, board)) { this.addRaw(thread, board); } } } } this.blacklisted = blacklisted; this.build(true); this.refresh(); }; ThreadWatcher.refresh = function() { var i, to, key, total, img; if (total = $.id('watchList').children.length) { i = to = 0; img = $.id('twPrune'); img.className = 'icon rotateIcon'; ThreadWatcher.isRefreshing = true; ThreadWatcher.setRefreshTimestamp(); for (key in ThreadWatcher.watched) { setTimeout(ThreadWatcher.fetch, to, key, ++i == total ? img : null); to += 200; } } }; ThreadWatcher.onRefreshEnd = function(img) { img.className = 'icon refreshIcon'; this.isRefreshing = false; this.save(); this.load(); this.build(); }; ThreadWatcher.parseThreadJSON = function(data) { var thread; try { thread = JSON.parse(data).posts; } catch (e) { console.log(e); thread = []; } return thread; }; ThreadWatcher.getTrackedReplies = function(board, tid) { var tracked = null; if (tracked = localStorage.getItem('4chan-track-' + board + '-' + tid)) { tracked = JSON.parse(tracked); } return tracked; }; ThreadWatcher.fetch = function(key, img) { var tuid, xhr, li; li = $.id('watch-' + key); if (ThreadWatcher.watched[key][1] == -1) { delete ThreadWatcher.watched[key]; li.parentNode.removeChild(li); if (img) { ThreadWatcher.onRefreshEnd(img); } return; } tuid = key.split('-'); // tid, board xhr = new XMLHttpRequest(); xhr.onload = function() { var i, newReplies, posts, lastReply, trackedReplies, dummy, quotelinks, q, j; if (this.status == 200) { posts = ThreadWatcher.parseThreadJSON(this.responseText); lastReply = ThreadWatcher.watched[key][1]; newReplies = 0; if (!ThreadWatcher.watched[key][4]) { trackedReplies = ThreadWatcher.getTrackedReplies(tuid[1], tuid[0]); if (trackedReplies) { dummy = document.createElement('div'); } } else { trackedReplies = null; } for (i = posts.length - 1; i >= 1; i--) { if (posts[i].no <= lastReply) { break; } ++newReplies; if (trackedReplies) { dummy.innerHTML = posts[i].com; quotelinks = $.cls('quotelink', dummy); if (!quotelinks[0]) { continue; } for (j = 0; q = quotelinks[j]; ++j) { if (trackedReplies[q.textContent]) { ThreadWatcher.watched[key][4] = 1; trackedReplies = null; break; } } } } if (newReplies > ThreadWatcher.watched[key][2]) { ThreadWatcher.watched[key][2] = newReplies; } if (posts[0].archived) { ThreadWatcher.watched[key][3] = 1; } } else if (this.status == 404) { ThreadWatcher.watched[key][1] = -1; } if (img) { ThreadWatcher.onRefreshEnd(img); } }; if (img) { xhr.onerror = xhr.onload; } xhr.open('GET', '//a.4cdn.org/' + tuid[1] + '/thread/' + tuid[0] + '.json'); xhr.send(null); }; ThreadWatcher.linkToThread = function(tid, board, post) { return '//' + location.host + '/' + board + '/thread/' + tid + (post > 0 ? ('#p' + post) : ''); }; /** * Draggable helper */ var Draggable = { el: null, key: null, scrollX: null, scrollY: null, dx: null, dy: null, right: null, bottom: null, set: function(handle) { handle.addEventListener('mousedown', Draggable.startDrag, false); }, unset: function(handle) { handle.removeEventListener('mousedown', Draggable.startDrag, false); }, startDrag: function(e) { var self, doc, offs; if (this.parentNode.hasAttribute('data-shiftkey') && !e.shiftKey) { return; } e.preventDefault(); self = Draggable; doc = document.documentElement; self.el = this.parentNode; self.key = self.el.getAttribute('data-trackpos'); offs = self.el.getBoundingClientRect(); self.dx = e.clientX - offs.left; self.dy = e.clientY - offs.top; self.right = doc.clientWidth - offs.width; self.bottom = doc.clientHeight - offs.height; if (getComputedStyle(self.el, null).position != 'fixed') { self.scrollX = window.pageXOffset; self.scrollY = window.pageYOffset; } else { self.scrollX = self.scrollY = 0; } self.offsetTop = FC.getDocTopOffset(); document.addEventListener('mouseup', self.endDrag, false); document.addEventListener('mousemove', self.onDrag, false); }, endDrag: function() { document.removeEventListener('mouseup', Draggable.endDrag, false); document.removeEventListener('mousemove', Draggable.onDrag, false); if (Draggable.key && window.Config) { window.Config[Draggable.key] = Draggable.el.style.cssText; localStorage.setItem('4chan-settings', JSON.stringify(window.Config)); //StorageSync.sync('4chan-settings'); } delete Draggable.el; }, onDrag: function(e) { var left, top, style; left = e.clientX - Draggable.dx + Draggable.scrollX; top = e.clientY - Draggable.dy + Draggable.scrollY; style = Draggable.el.style; if (left < 1) { style.left = '0'; style.right = ''; } else if (Draggable.right < left) { style.left = ''; style.right = '0'; } else { style.left = (left / document.documentElement.clientWidth * 100) + '%'; style.right = ''; } if (top <= Draggable.offsetTop) { style.top = Draggable.offsetTop + 'px'; style.bottom = ''; } else if (Draggable.bottom < top && Draggable.el.clientHeight < document.documentElement.clientHeight) { style.bottom = '0'; style.top = ''; } else { style.top = (top / document.documentElement.clientHeight * 100) + '%'; style.bottom = ''; } } }; /** * Custom Menu */ var CustomMenu = { dropDownNav: false, classicNav: false }; CustomMenu.initCtrl = function(dropDownNav, classicNav) { var el, cnt; CustomMenu.dropDownNav = dropDownNav; CustomMenu.classicNav = classicNav; el = document.createElement('span'); el.className = 'custom-menu-ctrl'; el.innerHTML = '[Edit]'; if (CustomMenu.dropDownNav && !CustomMenu.classicNav && !FC.hasMobileLayout) { cnt = $.id('boardSelectMobile').parentNode; cnt.insertBefore(el, cnt.lastChild); } else { cnt = $.cls('boardList'); cnt[0] && cnt[0].appendChild(el); cnt[1] && cnt[1].appendChild(el.cloneNode(true)); } }; /* CustomMenu.showNWSBoards = function() { var i, el, nodes, len; nodes = $.cls('nwsb'); len = nodes.length; for (i = len - 1; el = nodes[i]; i--) { $.removeClass(el, 'nwsb'); } }; */ CustomMenu.reset = function() { var i, el, full, custom, navs; full = $.cls('boardList'); custom = $.cls('customBoardList'); navs = $.cls('show-all-boards'); for (i = 0; el = navs[i]; ++i) { el.removeEventListener('click', CustomMenu.reset, false); } for (i = custom.length - 1; el = custom[i]; i--) { full[i].style.display = null; el.parentNode.removeChild(el); } }; CustomMenu.apply = function(str) { var i, el, cntBottom, board, navs, boardList, cnt; if (!str) { if (CustomMenu.dropDownNav && !CustomMenu.classicNav && !FC.hasMobileLayout) { if (el = $.cls('customBoardList')[0]) { el.parentNode.removeChild(el); } } return; } boardList = str.split(/[^0-9a-z]/i); cnt = document.createElement('span'); cnt.className = 'customBoardList'; for (i = 0; board = boardList[i]; ++i) { if (i) { cnt.appendChild(document.createTextNode(' / ')); } else { cnt.appendChild(document.createTextNode('[')); } el = document.createElement('a'); el.textContent = board; el.href = '//boards.' + $L.d(board) + '/' + board + (board !== 'f' ? '/catalog' : ''); cnt.appendChild(el); } cnt.appendChild(document.createTextNode(']')); if (CustomMenu.dropDownNav && !CustomMenu.classicNav && !FC.hasMobileLayout) { if (el = $.cls('customBoardList')[0]) { el.parentNode.removeChild(el); } navs = $.id('boardSelectMobile'); navs && navs.parentNode.insertBefore(cnt, navs.nextSibling); } else { cnt.appendChild(document.createTextNode(' [')); el = document.createElement('a'); el.textContent = '…'; el.title = 'Show all'; el.className = 'show-all-boards pointer'; cnt.appendChild(el); cnt.appendChild(document.createTextNode('] ')); cntBottom = cnt.cloneNode(true); navs = $.cls('boardList'); for (i = 0; el = navs[i]; ++i) { el.style.display = 'none'; el.parentNode.insertBefore(i ? cntBottom : cnt, el); } navs = $.cls('show-all-boards'); for (i = 0; el = navs[i]; ++i) { el.addEventListener('click', CustomMenu.reset, false); } } }; CustomMenu.onClick = function(e) { var t; if ((t = e.target) == document) { return; } if (t.hasAttribute('data-close')) { CustomMenu.closeEditor(); } else if (t.hasAttribute('data-save')) { CustomMenu.save($.id('customMenu').hasAttribute('data-standalone')); } }; CustomMenu.showEditor = function(standalone) { var cnt, extConfig; cnt = document.createElement('div'); cnt.id = 'customMenu'; cnt.className = 'panel'; cnt.setAttribute('data-close', '1'); if (standalone === true) { cnt.setAttribute('data-standalone', '1'); } cnt.innerHTML = '\
    Custom Board List\
    \ \
    '; document.body.appendChild(cnt); cnt.style.top = window.pageYOffset + (0 | (document.documentElement.clientHeight / 2) - (cnt.offsetHeight / 2)) + 'px'; $.removeClass($.id('backdrop'), 'hidden'); extConfig = CustomMenu.getConfig(); if (extConfig.customMenuList) { $.id('customMenuBox').value = extConfig.customMenuList; } cnt.addEventListener('click', CustomMenu.onClick, false); }; CustomMenu.closeEditor = function() { var el; if (el = $.id('customMenu')) { el.removeEventListener('click', CustomMenu.onClick, false); document.body.removeChild(el); $.addClass($.id('backdrop'), 'hidden'); } }; CustomMenu.save = function(standalone) { var input, extConfig; if (input = $.id('customMenuBox')) { if (standalone === true) { CustomMenu.apply(input.value); extConfig = CustomMenu.getConfig(); extConfig.customMenu = true; extConfig.customMenuList = input.value; localStorage.setItem('4chan-settings', JSON.stringify(extConfig)); //StorageSync.sync('4chan-settings'); } } CustomMenu.closeEditor(); }; CustomMenu.getConfig = function() { var extConfig; if (extConfig = localStorage.getItem('4chan-settings')) { return JSON.parse(extConfig); } else { return {}; } }; function checkMobileLayout() { var mobile, desktop; if (window.matchMedia) { return window.matchMedia('(max-width: 480px)').matches && localStorage.getItem('4chan_never_show_mobile') != 'true'; } mobile = $.id('boardNavMobile'); desktop = $.id('boardNavDesktop'); return mobile && desktop && mobile.offsetWidth > 0 && desktop.offsetWidth === 0; } var StickyNav = { thres: 5, pos: 0, timeout: null, el: null, init: function(classicNav) { this.el = classicNav ? $.id('boardNavDesktop') : $.id('boardNavMobile'); $.addClass(this.el, 'autohide-nav'); window.addEventListener('scroll', this.onScroll, false); }, destroy: function(classicNav) { this.el = classicNav ? $.id('boardNavDesktop') : $.id('boardNavMobile'); $.removeClass(this.el, 'autohide-nav'); window.removeEventListener('scroll', this.onScroll, false); }, onScroll: function() { clearTimeout(StickyNav.timeout); StickyNav.timeout = setTimeout(StickyNav.checkScroll, 50); }, checkScroll: function() { var thisPos; thisPos = window.pageYOffset; if (Math.abs(StickyNav.pos - thisPos) <= StickyNav.thres) { return; } if (thisPos < StickyNav.pos) { StickyNav.el.style.top = ''; } else { StickyNav.el.style.top = '-' + StickyNav.el.offsetHeight + 'px'; } StickyNav.pos = thisPos; } }; FC.panelHTML = { build: function(id, cls) { var el; el = document.createElement('div'); el.id = id; el.className = cls; el.innerHTML = FC.panelHTML[id]; document.body.appendChild(el); return el; }, 'theme': '
    Settings
    \

    Options

    \ \

    Shortcuts

    \ \

    Custom CSS

    \ \
    \ \
    \
    ', 'filters-protip': '
    Filters & Highlights Help
    \

    Patterns

    \ \
    \

    Controls

    \ ', 'filter-palette': '
    \ \ \ \
    Custom
    \ Close\ Clear\
    ', 'filters': '
    Filters & Highlights
    \ \ \ \ \ \ \ \ \ \ \ \ \ \
    OrderOnPatternBoardsColorHideTopDel
    \ \ \
    ' }; var PostMenu = { activeBtn: null }; PostMenu.open = function(btn, pid, hasThreadWatcher, hidden, pinned) { var div, html, btnPos, left, limit, tr; if (PostMenu.activeBtn == btn) { PostMenu.close(); return; } PostMenu.close(); tr = btn.parentNode.parentNode; html = ''; btnPos = btn.getBoundingClientRect(); div.style.top = btnPos.bottom + 3 + window.pageYOffset + 'px'; document.addEventListener('click', PostMenu.close, false); $.addClass(btn, 'menuOpen'); PostMenu.activeBtn = btn; UA.dispatchEvent('4chanPostMenuReady', { postId: pid, isOP: true, node: div.firstElementChild }); document.body.appendChild(div); left = btnPos.left + window.pageXOffset; limit = document.documentElement.clientWidth - div.offsetWidth; if (left > (limit - 75)) { div.className += ' dd-menu-left'; } if (left > limit) { left = limit; } div.style.left = left + 'px'; }; PostMenu.close = function() { var el; if (el = $.id('post-menu')) { el.parentNode.removeChild(el); document.removeEventListener('click', PostMenu.close, false); $.removeClass(PostMenu.activeBtn, 'menuOpen'); PostMenu.activeBtn = null; } };