/******************************** * * * 4chan Extension * * * ********************************/ /** * Helpers */ var $ = {}; $.id = function(id) { return document.getElementById(id); }; $.cls = function(klass, root) { return (root || document).getElementsByClassName(klass); }; $.byName = function(name) { return document.getElementsByName(name); }; $.tag = function(tag, root) { return (root || document).getElementsByTagName(tag); }; $.el = function(tag) { return document.createElement(tag); }; $.qs = function(sel, root) { return (root || document).querySelector(sel); }; $.qsa = function(selector, root) { return (root || document).querySelectorAll(selector); }; $.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); }; 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); }; } $.get = function(url, callbacks, headers) { var key, xhr; xhr = new XMLHttpRequest(); xhr.open('GET', url, true); if (callbacks) { for (key in callbacks) { xhr[key] = callbacks[key]; } } if (headers) { for (key in headers) { xhr.setRequestHeader(key, headers[key]); } } xhr.send(null); return xhr; }; $.xhr = function(method, url, callbacks, data) { var key, xhr, form; xhr = new XMLHttpRequest(); xhr.open(method, url, true); if (callbacks) { for (key in callbacks) { xhr[key] = callbacks[key]; } } if (data) { form = new FormData(); for (key in data) { form.append(key, data[key]); } data = form; } else { data = null; } xhr.send(data); return xhr; }; $.fit = function(w, h, maxW, maxH) { var r, outW, outH; r = w / h; if (w > maxW) { outW = maxW; outH = Math.round(outW / r); if (outH > maxH) { outH = maxH; outW = Math.round(outH * r); } } else if (h > maxH) { outH = maxH; outW = Math.round(outH * r); if (outW > maxW) { outW = maxW; outH = Math.round(outW / r); } } else { outW = w; outH = h; } return [outW, outH]; }; $.ago = function(timestamp) { var delta, count, head, tail; delta = Date.now() / 1000 - timestamp; if (delta < 1) { return 'moments ago'; } if (delta < 60) { return (0 | delta) + ' seconds ago'; } if (delta < 3600) { count = 0 | (delta / 60); if (count > 1) { return count + ' minutes ago'; } else { return 'one minute ago'; } } 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 + ' ago'; } 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 + ' ago'; }; $.hash = function(str) { var i, j, msg = 0; for (i = 0, j = str.length; i < j; ++i) { msg = ((msg << 5) - msg) + str.charCodeAt(i); } return msg; }; $.prettySeconds = function(fs) { var m, s; m = Math.floor(fs / 60); s = Math.round(fs - m * 60); return [ m, s ]; }; $.docEl = document.documentElement; $.cache = {}; /** * Parser */ var Parser = { tipTimeout: null }; Parser.init = function() { var o, a, h, m, tail, staticPath; if (Config.filter || Config.linkify || Config.embedSoundCloud || Config.embedYouTube || Main.hasMobileLayout) { this.needMsg = true; } staticPath = '//s.4cdn.org/image/'; tail = window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif'; this.icons = { admin: staticPath + 'adminicon' + tail, founder: staticPath + 'foundericon' + tail, mod: staticPath + 'modicon' + tail, dev: staticPath + 'developericon' + tail, manager: staticPath + 'managericon' + tail, del: staticPath + 'filedeleted-res' + tail }; this.prettify = typeof prettyPrint == 'function'; this.customSpoiler = {}; if (Config.localTime) { if (o = (new Date()).getTimezoneOffset()) { a = Math.abs(o); h = (0 | (a / 60)); this.utcOffset = 'Timezone: UTC' + (o < 0 ? '+' : '-') + h + ((m = a - h * 60) ? (':' + m) : ''); } else { this.utcOffset = 'Timezone: UTC'; } this.weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; } if (Main.tid) { this.trackedReplies = this.getTrackedReplies(Main.board, Main.tid); if (this.trackedReplies) { this.touchTrackedReplies(Main.tid); } else { this.trackedReplies = {}; } this.pruneTrackedReplies(); } this.postMenuIcon = Main.hasMobileLayout ? '...' : '▶'; }; Parser.getTrackedReplies = function(board, tid) { var tracked = null; if (tracked = localStorage.getItem('4chan-track-' + board + '-' + tid)) { tracked = JSON.parse(tracked); } return tracked; }; Parser.saveTrackedReplies = function(tid, replies) { var key = '4chan-track-' + Main.board + '-' + tid; localStorage.setItem(key, JSON.stringify(replies)); //StorageSync.sync(key); }; Parser.touchTrackedReplies = function(tid) { var tracked, key; key = '4chan-track-' + Main.board + '-ts'; if (tracked = localStorage.getItem(key)) { tracked = JSON.parse(tracked); } else { tracked = {}; } tracked[tid] = 0 | (Date.now() / 1000); localStorage.setItem(key, JSON.stringify(tracked)); }; Parser.pruneTrackedReplies = function() { var tid, tracked, now, thres, ttl, pfx, flag; pfx = '4chan-track-' + Main.board + '-'; if (tracked = localStorage.getItem(pfx + 'ts')) { ttl = 259200; now = 0 | (Date.now() / 1000); thres = now - ttl; flag = false; tracked = JSON.parse(tracked); if (Main.tid && tracked[Main.tid]) { tracked[Main.tid] = now; flag = true; } for (tid in tracked) { if (tracked[tid] <= thres) { flag = true; delete tracked[tid]; localStorage.removeItem(pfx + tid); //StorageSync.queue.push(pfx + tid); } } if (flag) { localStorage.removeItem(pfx + 'ts'); for (tid in tracked) { localStorage.setItem(pfx + 'ts', JSON.stringify(tracked)); break; } //StorageSync.queue.push(pfx + 'ts'); } //StorageSync.send(); } }; Parser.parseThreadJSON = function(data) { var thread; try { thread = JSON.parse(data).posts; } catch (e) { console.log(e); thread = []; } return thread; }; Parser.parseCatalogJSON = function(data) { var catalog; try { catalog = JSON.parse(data); } catch (e) { console.log(e); catalog = []; } return catalog; }; Parser.setCustomSpoiler = function(board, val) { var s; if (!this.customSpoiler[board] && (val = parseInt(val))) { if (board == Main.board && (s = $.cls('imgspoiler')[0])) { this.customSpoiler[board] = s.firstChild.src.match(/spoiler(-[a-z0-9]+)\.png$/)[1]; } else { this.customSpoiler[board] = '-' + board + (Math.floor(Math.random() * val) + 1); } } }; Parser.buildPost = function(thread, board, pid) { var i, j, uid, el = null; for (i = 0; j = thread[i]; ++i) { if (j.no != pid) { continue; } if (!Config.revealSpoilers && thread[0].custom_spoiler) { Parser.setCustomSpoiler(board, thread[0].custom_spoiler); } el = Parser.buildHTMLFromJSON(j, board, false, true).lastElementChild; if (Config.IDColor && (uid = $.cls('posteruid', el)[Main.hasMobileLayout ? 0 : 1])) { IDColor.applyRemote(uid.firstElementChild); } } return el; }; Parser.decodeSpecialChars = function(str) { return str.replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/</g, '<') .replace(/>/g, '>'); }; Parser.encodeSpecialChars = function(str) { return str.replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>'); }; Parser.onDateMouseOver = function(el) { if (Parser.tipTimeout) { clearTimeout(Parser.tipTimeout); Parser.tipTimeout = null; } Parser.tipTimeout = setTimeout(Tip.show, 500, el, $.ago(+el.getAttribute('data-utc'))); }; Parser.onTipMouseOut = function() { if (Parser.tipTimeout) { clearTimeout(Parser.tipTimeout); Parser.tipTimeout = null; } }; Parser.onUIDMouseOver = function(el) { var p; if (!$.hasClass(el.parentNode, 'posteruid')) { return; } if (!Main.tid) { p = el.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode; if (!$.hasClass(p, 'tExpanded')) { return; } } if (Parser.tipTimeout) { clearTimeout(Parser.tipTimeout); Parser.tipTimeout = null; } Parser.tipTimeout = setTimeout(Parser.showUIDCount, 500, el, el.textContent); }; Parser.showUIDCount = function(t, uid) { var i, el, nodes, count, msg; count = 0; nodes = $.qsa('.postInfo .hand'); for (i = 0; el = nodes[i]; ++i) { if (el.textContent === uid) { ++count; } } msg = count + ' post' + (count != 1 ? 's' : '') + ' by this ID'; Tip.show(t, msg); }; Parser.buildHTMLFromJSON = function(data, board, standalone, fromQuote) { var container = document.createElement('div'), isOP = false, userId, fileDims = '', imgSrc = '', fileInfo = '', fileHtml = '', fileThumb, filePath, fileName, fileSpoilerTip = '"', size = '', fileClass = '', shortFile = '', longFile = '', tripcode = '', capcodeStart = '', capcodeClass = '', capcode = '', flag, highlight = '', emailStart = '', emailEnd = '', name, mName, subject, noLink, quoteLink, replySpan = '', noFilename, decodedFilename, mobileLink = '', postType = 'reply', summary = '', postCountStr, resto, capcode_replies = '', threadIcons = '', boardTag = '', needFileTip = false, i, q, href, quotes, tmp, imgDir; /* if (board !== 'f') { if (data.no % 3 > 2) { imgDir = '//is.4chan.org/' + board; } else { imgDir = '//is2.4chan.org/' + board; } } else {*/ imgDir = '//i.4cdn.org/' + board; //} if (!data.resto) { isOP = true; if (standalone) { if (data.replies > 0) { tmp = data.replies + ' Repl' + (data.replies > 1 ? 'ies' : 'y'); if (data.images > 0) { tmp += ' / ' + data.images + ' Image' + (data.images > 1 ? 's' : ''); } } else { tmp = ''; } mobileLink = ''; postType = 'op'; replySpan = '  [Reply]'; } if (board != Main.board) { boardTag = '/' + board + '/ '; } resto = data.no; } else { resto = data.resto; } if (!Main.tid || board != Main.board) { noLink = '//boards.' + $L.d(board) + '/' + board + '/thread/' + resto + '#p' + data.no; quoteLink = '//boards.' + $L.d(board) + '/' + board + '/thread/' + resto + '#q' + data.no; } else { noLink = '#p' + data.no; quoteLink = 'javascript:quote(\'' + data.no + '\')'; } if (!data.capcode && data.id) { userId = ' (ID: ' + data.id + ') '; } else { userId = ''; } switch (data.capcode) { case 'admin_highlight': highlight = ' highlightPost'; /* falls through */ case 'admin': capcodeStart = ' ## Admin'; capcodeClass = ' capcodeAdmin'; capcode = ' '; break; case 'mod': capcodeStart = ' ## Mod'; capcodeClass = ' capcodeMod'; capcode = ' '; break; case 'developer': capcodeStart = ' ## Developer'; capcodeClass = ' capcodeDeveloper'; capcode = ' '; break; case 'manager': capcodeStart = ' ## Manager'; capcodeClass = ' capcodeManager'; capcode = ' '; break; case 'founder': capcodeStart = ' ## Founder'; capcodeClass = ' capcodeAdmin'; capcode = ' '; break; case 'verified': capcodeStart = ' ## Verified'; capcodeClass = ' capcodeVerified'; capcode = ''; break; } if (data.email) { emailStart = ''; emailEnd = ''; } if (data.flag_name) { flag = ' '; } else if (data.country_name) { flag = ' '; } else { flag = ''; } if (data.filedeleted) { fileHtml = '
File deleted.
'; } else if (data.ext) { decodedFilename = Parser.decodeSpecialChars(data.filename); shortFile = longFile = data.filename + data.ext; if (decodedFilename.length > (isOP ? 40 : 30)) { shortFile = Parser.encodeSpecialChars( decodedFilename.slice(0, isOP ? 35 : 25) ) + '(...)' + data.ext; needFileTip = true; } if (!data.tn_w && !data.tn_h && data.ext == '.gif') { data.tn_w = data.w; data.tn_h = data.h; } if (data.fsize >= 1048576) { size = ((0 | (data.fsize / 1048576 * 100 + 0.5)) / 100) + ' M'; } else if (data.fsize > 1024) { size = (0 | (data.fsize / 1024 + 0.5)) + ' K'; } else { size = data.fsize + ' '; } if (data.spoiler) { if (!Config.revealSpoilers) { fileName = 'Spoiler Image'; fileSpoilerTip = '" title="' + longFile + '"'; fileClass = ' imgspoiler'; fileThumb = '//s.4cdn.org/image/spoiler' + (Parser.customSpoiler[board] || '') + '.png'; data.tn_w = 100; data.tn_h = 100; noFilename = true; } else { fileName = shortFile; } } else { fileName = shortFile; } if (!fileThumb) { fileThumb = '//i.4cdn.org/' + board + '/' + data.tim + 's.jpg'; } fileDims = data.ext == '.pdf' ? 'PDF' : data.w + 'x' + data.h; if (board != 'f') { filePath = imgDir + '/' + data.tim + data.ext; imgSrc = '' + size + 'B' + '
' + size + 'B ' + data.ext.slice(1).toUpperCase() + '
'; fileInfo = '
' + fileName + ' (' + size + 'B, ' + fileDims + ')
'; } else { filePath = imgDir + '/' + data.filename + data.ext; fileDims += ', ' + data.tag; fileInfo = '
File: ' + data.filename + '.swf (' + size + 'B, ' + fileDims + ')
'; } fileHtml = '
' + fileInfo + imgSrc + '
'; } if (data.trip) { tripcode = ' ' + data.trip + ''; } name = data.name || ''; if (Main.hasMobileLayout && name.length > 30) { mName = '' + Parser.truncate(name, 30) + '(...) '; } else { mName = '' + name + ' '; } if (isOP) { if (data.capcode_replies) { capcode_replies = Parser.buildCapcodeReplies(data.capcode_replies, board, data.no); } if (fromQuote && data.replies) { postCountStr = data.replies + ' repl' + (data.replies > 1 ? 'ies' : 'y'); if (data.images) { postCountStr += ' and ' + data.images + ' image' + (data.images > 1 ? 's' : ''); } summary = '' + postCountStr + '.'; } if (data.sticky) { threadIcons += 'Sticky '; } if (data.closed) { if (data.archived) { threadIcons += 'Archived '; } else { threadIcons += 'Closed '; } } if (data.sub === undefined) { subject = ' '; } else if (Main.hasMobileLayout && data.sub.length > 30) { subject = '' + Parser.truncate(data.sub, 30) + '(...) '; } else { subject = '' + data.sub + ' '; } } else { subject = ''; } container.className = 'postContainer ' + postType + 'Container'; container.id = 'pc' + data.no; if (data.xa24) { container.className += ' p-xa24-' + data.xa24; } container.innerHTML = (isOP ? '' : '
>>
') + '
' + '
' + '' + boardTag + mName + tripcode + capcodeStart + capcode + userId + flag + '
' + subject + '
' + data.now + ' No.' + data.no + '' + '
' + (isOP ? fileHtml : '') + '
' + ' ' + boardTag + subject + '' + emailStart + '' + name + '' + tripcode + (data.since4pass ? (' ') : '') + (data.xa24 ? (' ') : '') + capcodeStart + emailEnd + capcode + userId + flag + ' ' + '' + data.now + ' ' + '' + 'No.' + data.no + '' + threadIcons + replySpan + '' + '
' + (isOP ? '' : fileHtml) + '
' + (data.com || '') + capcode_replies + summary + '
' + '
' + mobileLink; if (!Main.tid || board != Main.board) { quotes = container.getElementsByClassName('quotelink'); for (i = 0; q = quotes[i]; ++i) { href = q.getAttribute('href'); if (href.charAt(0) != '/') { q.href = '//boards.' + $L.d(board) + '/' + board + '/thread/' + resto + href; } } } return container; }; Parser.truncate = function(str, len) { str = str.replace(',', ','); str = Parser.decodeSpecialChars(str); str = str.slice(0, len); str = Parser.encodeSpecialChars(str); return str; }; Parser.buildCapcodeReplies = function(replies, board, tid) { var i, capcode, id, html, map, post_ids, prelink, pretext; map = { admin: 'Administrator', mod: 'Moderator', developer: 'Developer', manager: 'Manager' }; if (board != Main.board) { prelink = '/' + board + '/thread/'; pretext = '>>>/' + board + '/'; } else { prelink = ''; pretext = '>>'; } html = '

'; for (capcode in replies) { html += '' + map[capcode] + ' Replies: '; post_ids = replies[capcode]; for (i = 0; id = post_ids[i]; ++i) { html += '' + pretext + id + ' '; } } return html + ''; }; Parser.parseBoard = function() { var i, threads = document.getElementsByClassName('thread'); for (i = 0; threads[i]; ++i) { Parser.parseThread(threads[i].id.slice(1)); } }; Parser.parseThread = function(tid, offset, limit) { var i, j, thread, posts, pi, el, frag, summary, omitted, key, filtered, cnt; thread = $.id('t' + tid); posts = thread.getElementsByClassName('post'); if (!offset) { pi = document.getElementById('pi' + tid); if (!Main.tid) { if (Config.filter) { filtered = Filter.exec( thread, pi, document.getElementById('m' + tid), tid ); } if (Config.threadHiding && !filtered) { if (!Main.hasMobileLayout) { el = document.createElement('span'); el.innerHTML = 'H'; posts[0].insertBefore(el, posts[0].firstChild); el.id = 'sa' + tid; } if (ThreadHiding.hidden[tid]) { ThreadHiding.hidden[tid] = Main.now; ThreadHiding.hide(tid); } } if (ThreadExpansion.enabled && (summary = $.cls('summary', thread)[0])) { frag = document.createDocumentFragment(); omitted = summary.cloneNode(true); omitted.className = ''; summary.textContent = ''; el = document.createElement('img'); el.className = 'extButton expbtn'; el.title = 'Expand thread'; el.alt = '+'; el.setAttribute('data-cmd', 'expand'); el.setAttribute('data-id', tid); el.src = Main.icons.plus; frag.appendChild(el); frag.appendChild(omitted); el = document.createElement('span'); el.style.display = 'none'; el.textContent = 'Showing all replies.'; frag.appendChild(el); summary.appendChild(frag); } } if (Main.tid && Config.threadWatcher) { el = document.createElement('img'); if (ThreadWatcher.watched[key = tid + '-' + Main.board]) { el.src = Main.icons.watched; el.setAttribute('data-active', '1'); } else { el.src = Main.icons.notwatched; } el.className = 'extButton wbtn wbtn-' + key; el.setAttribute('data-cmd', 'watch'); el.setAttribute('data-id', tid); el.alt = 'W'; el.title = 'Add to watch list'; cnt = $.cls('navLinks'); for (i = 1; i < 3 && (j = cnt[i]); ++i) { frag = document.createDocumentFragment(); frag.appendChild(document.createTextNode('[')); frag.appendChild(el.cloneNode(true)); frag.appendChild(document.createTextNode('] ')); j.insertBefore(frag, j.firstChild); } } } j = offset ? offset < 0 ? posts.length + offset : offset : 0; limit = limit ? j + limit : posts.length; if (Main.isMobileDevice && Config.quotePreview) { for (i = j; i < limit; ++i) { Parser.parseMobileQuotelinks(posts[i]); } } if (Parser.trackedReplies) { for (i = j; i < limit; ++i) { Parser.parseTrackedReplies(posts[i]); } } for (i = j; i < limit; ++i) { Parser.parsePost(posts[i].id.slice(1), tid); } if (offset) { if (Parser.prettify) { for (i = j; i < limit; ++i) { Parser.parseMarkup(posts[i]); } } if (window.math_tags) { if (window.MathJax) { for (i = j; i < limit; ++i) { if (Parser.postHasMath(posts[i])) { window.cleanWbr(posts[i]); } MathJax.Hub.Queue(['Typeset', MathJax.Hub, posts[i]]); } } else { for (i = j; i < limit; ++i) { if (Parser.postHasMath(posts[i])) { window.loadMathJax(); } } } } } UA.dispatchEvent('4chanParsingDone', { threadId: tid, offset: j, limit: limit }); }; Parser.postHasMath = function(el) { return /\[(?:eqn|math)\]/.test(el.innerHTML); }; Parser.parseMathOne = function(node) { if (window.MathJax) { MathJax.Hub.Queue(['Typeset', MathJax.Hub, node]); } else if (Parser.postHasMath(node)) { window.loadMathJax(); } }; Parser.parseTrackedReplies = function(post) { var i, link, quotelinks; quotelinks = $.cls('quotelink', post); for (i = 0; link = quotelinks[i]; ++i) { if (Parser.trackedReplies[link.textContent]) { link.className += ' ql-tracked'; link.textContent += ' (You)'; Parser.hasYouMarkers = true; } } }; Parser.parseMobileQuotelinks = function(post) { var i, link, quotelinks, t, el; quotelinks = $.cls('quotelink', post); for (i = 0; link = quotelinks[i]; ++i) { t = link.getAttribute('href').match(/(?:\/([a-z0-9]+)\/thread\/)?([0-9]+)?#p([0-9]+)$/); if (!t) { continue; } el = document.createElement('a'); el.href = link.href; el.textContent = ' #'; el.className = 'quoteLink'; link.parentNode.insertBefore(el, link.nextSibling); } }; Parser.parseMarkup = function(post) { var i, pre, el; if ((pre = post.getElementsByClassName('prettyprint'))[0]) { for (i = 0; el = pre[i]; ++i) { el.innerHTML = window.prettyPrintOne(el.innerHTML); } } }; Parser.revealImageSpoiler = function(fileThumb) { var img, isOP, filename, finfo, txt; img = fileThumb.firstElementChild; fileThumb.removeChild(img); img.removeAttribute('style'); isOP = $.hasClass(fileThumb.parentNode.parentNode, 'op'); img.style.maxWidth = img.style.maxHeight = isOP ? '250px' : '125px'; img.src = '//i.4cdn.org' + (fileThumb.pathname.replace(/\/([0-9]+).+$/, '/$1s.jpg')); filename = fileThumb.previousElementSibling; finfo = filename.title.split('.'); if (finfo[0].length > (isOP ? 40 : 30)) { txt = finfo[0].slice(0, isOP ? 35 : 25) + '(...)' + finfo[1]; } else { txt = filename.title; filename.removeAttribute('title'); } filename.firstElementChild.innerHTML = txt; fileThumb.insertBefore(img, fileThumb.firstElementChild); }; Parser.parsePost = function(pid, tid) { var hasMobileLayout, cnt, el, pi, file, msg, filtered, uid; hasMobileLayout = Main.hasMobileLayout; if (!tid) { pi = pid.getElementsByClassName('postInfo')[0]; pid = pi.id.slice(2); } else { pi = document.getElementById('pi' + pid); } if (Parser.needMsg) { msg = document.getElementById('m' + pid); } el = document.createElement('a'); el.href = '#'; el.className = 'postMenuBtn'; el.title = 'Post menu'; el.setAttribute('data-cmd', 'post-menu'); el.textContent = Parser.postMenuIcon; if (hasMobileLayout) { cnt = document.getElementById('pim' + pid); cnt.insertBefore(el, cnt.firstElementChild); } else { pi.appendChild(el); } if (tid) { if (pid != tid) { if (Config.filter) { filtered = Filter.exec(pi.parentNode, pi, msg); } if (!filtered && ReplyHiding.hidden[pid]) { ReplyHiding.hidden[pid] = Main.now; ReplyHiding.hide(pid); } /* if (ReplyHiding.hiddenR[pid]) { ReplyHiding.hideR(pid, pid); } else if (ReplyHiding.hasR) { if (ppid = ReplyHiding.shouldToggleR(msg)) { ReplyHiding.hideR(pid, ppid); } } */ } if (Config.backlinks) { Parser.parseBacklinks(pid, tid); } if (Main.isOekakiBoard && Main.tid) { Parser.addOekakiEditLink(pid, tid); } } if (IDColor.enabled && (uid = $.cls('posteruid', pi.parentNode)[hasMobileLayout ? 0 : 1])) { IDColor.apply(uid.firstElementChild); } if (Config.linkify) { Linkify.exec(msg); } if (Config.embedSoundCloud) { Media.parseSoundCloud(msg); } if (Config.embedYouTube || hasMobileLayout) { Media.parseYouTube(msg); } if (Config.revealSpoilers && (file = document.getElementById('f' + pid)) && (file = file.children[1]) ) { if ($.hasClass(file, 'imgspoiler')) { Parser.revealImageSpoiler(file); } } if (Config.localTime) { if (hasMobileLayout) { el = pi.parentNode.getElementsByClassName('dateTime')[0]; el.firstChild.nodeValue = Parser.getLocaleDate(new Date(el.getAttribute('data-utc') * 1000)) + ' '; } else { el = pi.getElementsByClassName('dateTime')[0]; //el.title = this.utcOffset; el.textContent = Parser.getLocaleDate(new Date(el.getAttribute('data-utc') * 1000)); } } }; Parser.addOekakiEditLink = function(pid, tid) { var cnt, el, a; cnt = $.id('fT' + pid); if (!cnt) { return; } a = cnt.firstElementChild; if (!a.href || !/\.(png|jpg)$/.test(a.href)) { return; } el = $.el('small'); el.innerHTML = ' Edit'; cnt.appendChild(el); }; Parser.getLocaleDate = function(date) { return ('0' + (1 + date.getMonth())).slice(-2) + '/' + ('0' + date.getDate()).slice(-2) + '/' + ('0' + date.getFullYear()).slice(-2) + '(' + this.weekdays[date.getDay()] + ')' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2); }; Parser.parseBacklinks = function(pid, tid) { var i, j, msg, backlinks, linklist, ids, target, bl, el, href; msg = document.getElementById('m' + pid); if (!(backlinks = msg.getElementsByClassName('quotelink'))) { return; } linklist = {}; for (i = 0; j = backlinks[i]; ++i) { // [tid, pid] ids = j.getAttribute('href').split('#p'); if (!ids[1]) { continue; } if (ids[1] == tid) { j.textContent += ' (OP)'; } if (!(target = document.getElementById('pi' + ids[1]))) { if (Main.tid && j.textContent.charAt(2) != '>' ) { j.textContent += ' →'; } continue; } // Already processed? if (linklist[ids[1]]) { continue; } linklist[ids[1]] = true; // Backlink node bl = document.createElement('span'); if (!Main.tid) { href = 'thread/' + tid + '#p' + pid; } else { href = '#p' + pid; } if (!Main.hasMobileLayout) { bl.innerHTML = '>>' + pid + ' '; } else { bl.innerHTML = '>>' + pid + ' # '; } // Backlinks container if (!(el = document.getElementById('bl_' + ids[1]))) { el = document.createElement('div'); el.id = 'bl_' + ids[1]; el.className = 'backlink'; if (Main.hasMobileLayout) { el.className = 'backlink mobile'; target = document.getElementById('p' + ids[1]); } target.appendChild(el); } el.appendChild(bl); } }; Parser.buildSummary = function(tid, oRep, oImg) { var el; if (oRep) { oRep = oRep + ' repl' + (oRep > 1 ? 'ies' : 'y'); } else { return null; } if (oImg) { oImg = ' and ' + oImg + ' image' + (oImg > 1 ? 's' : ''); } else { oImg = ''; } el = document.createElement('span'); el.className = 'summary desktop'; el.innerHTML = oRep + oImg + ' omitted. Click here to view.'; return el; }; var OgvCtrl = { ogv: null, cnt: null, ctrl: {}, seeking: false, visible: false, tick: null, attach: function(ogv) { this.detach(); ogv.parentNode.appendChild(this.cnt); $.on(ogv, 'mouseup', this.toggleCtrl); this.ogv = ogv; }, detach: function() { if (!this.ogv) { return; } this.ogv.stop(); $.off(this.ogv, 'mouseup', this.toggleCtrl); this.ctrl.play.classList.remove('ogv-toggled'); this.ctrl.mute.classList.remove('ogv-toggled'); this.hideCtrl(); this.ogv = null; this.seeking = false; this.cnt.remove(); }, init: function() { if (this.cnt) { return; } let cnt = $.el('div'); cnt.className = 'ogv-ctrl'; let el = $.el('div'); el.className = 'ogv-btn'; el.innerHTML = ''; $.on(el, 'click', this.togglePlay, false); this.ctrl.play = el; cnt.appendChild(el); el = $.el('input'); el.className = 'ogv-seek'; el.type = 'range'; el.min = 0; el.value = 0; el.max = 100; el.step = 0.1; $.on(el, 'change', this.onSeek, false); $.on(el, 'mousedown', this.toggleSeek, false); $.on(el, 'mouseup', this.toggleSeek, false); this.ctrl.seek = el; cnt.appendChild(el); el = $.el('div'); el.className = 'ogv-ts'; el.textContent = '0:00 / 0:00'; this.ctrl.ts = el; cnt.appendChild(el); el = $.el('div'); el.className = 'ogv-btn'; el.innerHTML = ''; $.on(el, 'click', this.toggleMute, false); this.ctrl.mute = el; cnt.appendChild(el); el = $.el('input'); el.className = 'ogv-vol'; el.type = 'range'; el.min = 0; el.value = 50; el.step = 0.1; el.max = 100; $.on(el, 'input', this.onVolInput, false); this.ctrl.vol = el; cnt.appendChild(el); el = $.el('div'); el.className = 'ogv-btn'; el.innerHTML = ''; $.on(el, 'click', this.toggleFullscreen, false); this.ctrl.fs = el; cnt.appendChild(el); this.cnt = cnt; }, onPlayEnd: function() { if (OgvCtrl.ogv.seekable.length) { OgvCtrl.ogv.currentTime = 0; } else { OgvCtrl.ogv.stop(); } OgvCtrl.ogv.play(); }, toggleCtrl: function() { if (OgvCtrl.visible) { OgvCtrl.hideCtrl(); } else { OgvCtrl.cnt.style.display = 'flex'; OgvCtrl.setTickTimeout(); OgvCtrl.updateTimes(); OgvCtrl.visible = true; } }, hideCtrl: function() { OgvCtrl.cnt.style.display = 'none'; OgvCtrl.clearTickTimeout(); OgvCtrl.visible = false; }, toggleSeek: function() { OgvCtrl.seeking = !OgvCtrl.seeking; }, seekTick: function() { OgvCtrl.setTickTimeout(); OgvCtrl.updateTimes(); }, setTickTimeout: function() { OgvCtrl.tick = setTimeout(OgvCtrl.seekTick, 500); }, clearTickTimeout: function() { clearTimeout(OgvCtrl.tick); OgvCtrl.tick = null; }, updateTimes: function() { if (!OgvCtrl.ogv.duration) { return; } if (!OgvCtrl.seeking) { OgvCtrl.ctrl.seek.value = ((OgvCtrl.ogv.currentTime / OgvCtrl.ogv.duration) * 100).toFixed(2); } let dm = Math.floor(OgvCtrl.ogv.duration / 60); let ds = Math.floor(OgvCtrl.ogv.duration - dm * 60); let m = Math.floor(OgvCtrl.ogv.currentTime / 60); let s = Math.floor(OgvCtrl.ogv.currentTime - m * 60); OgvCtrl.ctrl.ts.textContent = `${m}:${s.toString().padStart(2, '0')} / ${dm}:${ds.toString().padStart(2, '0')}`; }, togglePlay: function() { if (OgvCtrl.ogv.paused) { OgvCtrl.ogv.play(); } else { OgvCtrl.ogv.pause(); } OgvCtrl.ctrl.play.classList.toggle('ogv-toggled'); }, onSeek: function() { OgvCtrl.ogv.currentTime = (this.value / 100) * OgvCtrl.ogv.duration; }, toggleMute: function() { OgvCtrl.ogv.muted = !OgvCtrl.ogv.muted; OgvCtrl.ctrl.mute.classList.toggle('ogv-toggled'); }, onVolInput: function() { OgvCtrl.ogv.volume = this.value / 100; }, toggleFullscreen: function() { if (document.fullscreenElement) { document.exitFullscreen(); } else { OgvCtrl.ogv.parentNode.requestFullscreen(); } OgvCtrl.ctrl.fs.classList.toggle('ogv-toggled'); } }; /** * Post Menu */ var PostMenu = { activeBtn: null }; PostMenu.open = function(btn) { var div, html, pid, board, btnPos, el, href, left, limit, isOP, file; if (PostMenu.activeBtn == btn) { PostMenu.close(); return; } PostMenu.close(); pid = btn.parentNode.id.replace(/^[0-9]*[^0-9]+/, ''); board = btn.parentNode.getAttribute('data-board'); isOP = !board && !!$.id('t' + pid); 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: isOP, node: div.firstElementChild }); document.body.appendChild(div); left = btnPos.left + window.pageXOffset; limit = $.docEl.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; } }; /** * */ var Search = { xhr: null, pageSize: 10, maxPages: 10, init: function() { var el; if (el = $.id('g-search-form')) { $.on(el, 'submit', Search.onSearch); $.on(window, 'hashchange', Search.onHashChanged); if (location.host == 'boards.4channel.org') { Search.initSelector(); } Search.initFromURL(true); } }, initSelector: function() { var i, el, nodes, sel, len; sel = $.id('js-sf-bf'); nodes = sel.options; len = nodes.length; for (i = len - 1; i >= 0; i--) { el = nodes[i]; if (el.value === '' || $L.d(el.value) === '4chan.org') { sel.removeChild(el); } } }, initFromURL: function(init) { var self, frag, i, el, nodes, opt; self = Search; self.query = ''; self.board = ''; self.offset = 0; frag = window.location.hash; if (frag !== '' && frag.length <= 512) { frag = frag.split('/').slice(1); if (frag[0]) { self.query = decodeURIComponent(frag[0]); } else { self.query = ''; } self.board = frag[1] || ''; if (frag[1]) { if (frag[1] === 'all') { self.board = ''; } else { self.board = frag[1]; } } self.offset = self.pageToOffset(0 | frag[2]); } if (init && self.query === '') { return; } $.id('js-sf-qf').value = self.query; el = $.id('js-sf-bf'); el.selectedIndex = 0; for (i = 0, nodes = el.options; opt = nodes[i]; ++i) { if (opt.value === self.board) { el.selectedIndex = i; break; } } if (el.selectedIndex === 0 && self.board !== '') { self.board = ''; } Search.exec(self.query, self.board, self.offset); }, onHashChanged: function() { Search.initFromURL(); }, pageToOffset: function(p) { if (p < 1 || p > Search.maxPages) { p = 1; } return (p - 1) * Search.pageSize; }, offsetToPage: function(o) { var p = o / Search.pageSize + 1; if (p < 1 || p > Search.maxPages) { p = 1; } return p; }, updateURL: function() { var self, frags = []; self = Search; if (self.query !== '') { frags.push(encodeURIComponent(self.query)); if (self.offset > 0) { if (!self.board || self.board === '') { frags.push('all'); } else { frags.push(self.board); } frags.push(Search.offsetToPage(self.offset)); } else if (self.board) { frags.push(self.board); } } if (frags.length) { window.history.replaceState(null, '', '#/' + frags.join('/')); } else { window.history.replaceState(null, '', window.location.href.replace(/#.*$/, '') ); } }, onSearch: function(e) { var qf, bf; e && e.preventDefault(); Search.query = qf = $.id('js-sf-qf').value; Search.board = bf = $.id('js-sf-bf').value; Search.exec(qf, bf, 0); }, exec: function(query, board, offset) { var self, qs = []; self = Search; self.toggleSpinner(false); self.updateCtrl(false); if (self.xhr) { self.xhr.abort(); self.xhr = null; } if (query === '') { return; } qs.push('q=' + encodeURIComponent(query)); if (board !== '') { qs.push('b=' + board); } if (offset) { qs.push('o=' + (0 | offset)); } qs = qs.join('&'); self.query = query; self.board = board; self.offset = offset; self.updateURL(); self.toggleSpinner(true); self.xhr = $.get('https://find.' + location.host.replace(/^boards\./, '') + '/api?' + qs, { onload: self.onLoad, onerror: self.onError, withCredentials: true }); }, onPageClick: function() { var offset; offset = +this.getAttribute('data-o'); Search.exec(Search.query, Search.board, offset); }, onLoad: function() { var data; Search.toggleSpinner(false); try { data = JSON.parse(this.responseText); } catch (err) { Search.showError('Something went wrong.'); console.log(err); return; } Search.buildResults(data); }, onError: function() { Search.toggleSpinner(false); Search.showError('Connection error.'); }, updateCtrl: function(offset, total) { var cnt, el, el2, maxPage, curPage; if (offset === false) { el = $.id('js-sf-pl'); if (el) { el.parentNode.removeChild(el); } return; } cnt = $.id('js-sf-pl'); if (cnt) { cnt.parentNode.removeChild(cnt); } cnt = $.el('div'); cnt.id = 'js-sf-pl'; if (!Main.hasMobileLayout) { cnt.className = 'pagelist desktop'; } else { cnt.className = 'mPagelist mobile'; } total = +total; offset = +offset; maxPage = Math.ceil(total / Search.pageSize); if (maxPage > Search.maxPages) { maxPage = Search.maxPages; } curPage = offset / Search.pageSize + 1; if (curPage > 1) { el = $.el('div'); el.className = 'prev'; if (!Main.hasMobileLayout) { el2 = $.el('input'); el2.type = 'button'; el2.value = 'Previous'; } else { el2 = $.el('a'); el2.className = 'button'; el2.textContent = 'Previous'; } el2.setAttribute('data-o', offset - Search.pageSize); $.on(el2, 'click', Search.onPageClick); el.appendChild(el2); cnt.appendChild(el); } el = $.el('div'); el.className = 'pages'; el.textContent = 'Page ' + curPage + ' / ' + maxPage; cnt.appendChild(el); if (curPage < maxPage) { el = $.el('div'); el.className = 'next'; if (!Main.hasMobileLayout) { el2 = $.el('input'); el2.type = 'button'; el2.value = 'Next'; } else { el2 = $.el('a'); el2.className = 'button'; el2.textContent = 'Next'; } el2.setAttribute('data-o', offset + Search.pageSize); $.on(el2, 'click', Search.onPageClick); el.appendChild(el2); cnt.appendChild(el); } el = $.id('delform'); el.parentNode.insertBefore(cnt, el.nextElementSibling); }, showStatus: function(msg, cls) { var cnt; cnt = $.cls('board')[0]; if (msg) { cnt.innerHTML = '
' + msg + '
'; } else { cnt.innerHTML = ''; } }, showError: function(msg) { Search.showStatus(msg, 'error'); }, toggleSpinner: function(flag) { var el = $.id('js-sf-btn'); if (flag) { el.disabled = true; Search.showStatus('Searching…', 'spnr'); } else { el.disabled = false; Search.showStatus(false); } }, buildResults: function(data) { var j, k, op, cnt, threads, thread, boardDiv, reply; threads = data.threads; if (threads.length < 1) { Search.showError('Nothing found.'); Search.updateCtrl(false); return; } boardDiv = $.cls('board')[0]; boardDiv.textContent = ''; for (j = 0; thread = threads[j]; ++j) { op = thread.posts[0]; cnt = $.el('div'); cnt.id = 't' + op.no; cnt.className = 'thread'; cnt.appendChild(Parser.buildHTMLFromJSON(op, thread.board, true)); for (k = 1; reply = thread.posts[k]; ++k) { cnt.appendChild(Parser.buildHTMLFromJSON(reply, thread.board)); } boardDiv.appendChild(cnt); boardDiv.appendChild($.el('hr')); } Search.updateCtrl(data.offset, data.nhits); } }; /** * Depager */ var Depager = {}; Depager.init = function() { var el, el2, cnt; this.isLoading = false; this.isEnabled = false; this.isComplete = false; this.threadsLoaded = false; this.threadQueue = []; this.debounce = 100; this.threshold = 350; this.adId = 'azk53379'; this.adZones = [ 16258, 16260 ]; this.boardHasAds = !!$.id(this.adId); if (this.boardHasAds) { el = $.cls('ad-plea'); this.adPlea = el[el.length - 1]; } if (Main.hasMobileLayout) { el = $.cls('next')[1]; if (!el) { return; } el = el.firstElementChild; el.textContent = 'Load More'; el.className += ' m-depagelink'; el.setAttribute('data-cmd', 'depage'); } else { el = $.cls('prev')[0]; if (!el) { return; } el.innerHTML = '[All]'; el = el.firstElementChild; } if (Config.alwaysDepage) { this.isEnabled = true; el.parentNode.parentNode.className += ' depagerEnabled'; Depager.bindHandlers(); if (!Main.hasMobileLayout && (cnt = $.cls('board')[0])) { el2 = document.createElement('span'); el2.className = 'depageNumber'; el2.textContent = 'Page 1'; cnt.insertBefore(el2, cnt.firstElementChild); } } else { el.setAttribute('data-cmd', 'depage'); } }; Depager.onScroll = function() { if (document.documentElement.scrollHeight <= (Math.ceil(window.innerHeight + window.pageYOffset) + Depager.threshold)) { if (Depager.threadsLoaded) { Depager.renderNext(); } else { Depager.depage(); } } }; Depager.trackPageview = function(pageId) { var url; try { if (window._gat) { url = '/' + Main.board + '/' + pageId; window._gat._getTrackerByName()._trackPageview(url); } if (window.__qc) { window.__qc.qpixelsent = []; window._qevents.push({ qacct: window.__qc.qopts.qacct }); window.__qc.firepixels(); } } catch(e) { console.log(e); } }; Depager.insertAd = function(pageId, frag, zone, isLastPage) { var wrap, cnt, nodes; if (!Depager.boardHasAds || !window.ados_add_placement) { return; } if (isLastPage) { nodes = $.cls('bottomad'); wrap = nodes[nodes.length - 1]; cnt = document.createElement('div'); cnt.id = 'azkDepage' + (pageId + 1); wrap.appendChild(cnt); window.ados_add_placement(3536, 18130, cnt.id, 4).setZone(zone); } wrap = document.createElement('div'); wrap.className = 'bottomad center depaged-ad'; if (pageId == 2) { cnt = $.id(Depager.adId); } else { cnt = document.createElement('div'); cnt.id = 'azkDepage' + pageId; } wrap.appendChild(cnt); frag.appendChild(wrap); if (Depager.adPlea) { frag.appendChild(Depager.adPlea.cloneNode(true)); } frag.appendChild(document.createElement('hr')); if (pageId != 2) { window.ados_add_placement(3536, 18130, cnt.id, 4).setZone(zone); } }; Depager.loadAds = function() { if (!Depager.boardHasAds || !window.ados_load) { return; } window.ados_load(); }; Depager.renderNext = function() { var el, frag, i, j, k, threads, op, summary, cnt, reply, parseList, scroll, last_replies, boardDiv, pageId, data, isLastPage; parseList = []; scroll = window.pageYOffset; frag = document.createDocumentFragment(); data = Depager.threadQueue.shift(); if (!data) { return; } threads = data.threads; pageId = data.page; isLastPage = !Depager.threadQueue.length; Depager.insertAd(pageId, frag, data.adZone, isLastPage); el = document.createElement('span'); el.className = 'depageNumber'; el.textContent = 'Page ' + pageId; frag.appendChild(el); for (j = 0; op = threads[j]; ++j) { if ($.id('t' + op.no)) { continue; } cnt = document.createElement('div'); cnt.id = 't' + op.no; cnt.className = 'thread'; cnt.appendChild(Parser.buildHTMLFromJSON(op, Main.board, true)); if (summary = Parser.buildSummary(op.no, op.omitted_posts, op.omitted_images)) { cnt.appendChild(summary); } if (op.replies) { last_replies = op.last_replies; for (k = 0; reply = last_replies[k]; ++k) { cnt.appendChild(Parser.buildHTMLFromJSON(reply, Main.board)); } } frag.appendChild(cnt); frag.appendChild(document.createElement('hr')); parseList.push(op.no); } if (isLastPage) { Depager.unbindHandlers(); Depager.isComplete = true; Depager.setStatus('disabled'); } boardDiv = $.cls('board')[0]; boardDiv.insertBefore(frag, boardDiv.lastElementChild); Depager.trackPageview(pageId); Depager.loadAds(); for (i = 0; op = parseList[i]; ++i) { Parser.parseThread(op); } window.scrollTo(0, scroll); }; Depager.bindHandlers = function() { window.addEventListener('scroll', Depager.onScroll, false); window.addEventListener('resize', Depager.onScroll, false); }; Depager.unbindHandlers = function() { window.removeEventListener('scroll', Depager.onScroll, false); window.removeEventListener('resize', Depager.onScroll, false); }; Depager.setStatus = function(type) { var i, el, links, p, caption; if (!Main.hasMobileLayout) { links = $.cls('depagelink'); caption = 'All'; } else { links = $.cls('m-depagelink'); caption = 'Load More'; } if (!links.length) { return; } if (type == 'enabled') { for (i = 0; el = links[i]; ++i) { el.textContent = caption; p = el.parentNode.parentNode; if (!$.hasClass(p, 'depagerEnabled')) { $.addClass(p,'depagerEnabled'); } } } else if (type == 'loading') { for (i = 0; el = links[i]; ++i) { el.textContent = 'Loading…'; } } else if (type == 'disabled') { for (i = 0; el = links[i]; ++i) { if (!Main.hasMobileLayout) { el.textContent = caption; $.removeClass(el.parentNode.parentNode,'depagerEnabled'); } else { el.parentNode.parentNode.removeChild(el.parentNode); } } } else if (type == 'error') { for (i = 0; el = links[i]; ++i) { el.textContent = 'Error'; el.removeAttribute('title'); el.removeAttribute('data-cmd'); $.removeClass(el.parentNode.parentNode, 'depagerEnabled'); } } }; Depager.toggle = function() { if (Depager.isLoading || Depager.isComplete) { return; } if (Depager.isEnabled) { Depager.disable(); } else { Depager.enable(); } Depager.isEnabled = !Depager.isEnabled; }; Depager.enable = function() { Depager.bindHandlers(); Depager.setStatus('enabled'); Depager.onScroll(); }; Depager.disable = function() { Depager.unbindHandlers(); Depager.setStatus('disabled'); }; Depager.depage = function() { if (Depager.isLoading) { return; } Depager.isLoading = true; $.get('//a.4cdn.org/' + Main.board + '/catalog.json', { onload: Depager.onLoad, onerror: Depager.onError }); Depager.setStatus('loading'); }; Depager.onLoad = function() { var catalog, i, page, queue, adZone; Depager.isLoading = false; Depager.threadsLoaded = true; if (this.status == 200) { Depager.setStatus('enabled'); if (!Config.alwaysDepage) { Depager.bindHandlers(); } catalog = Parser.parseCatalogJSON(this.responseText); queue = Depager.threadQueue; adZone = 0; for (i = 1; page = catalog[i]; ++i) { page.adZone = Depager.adZones[adZone]; queue.push(page); adZone = adZone ? 0 : 1; } Depager.renderNext(); } else if (this.status == 404) { Depager.unbindHandlers(); Depager.setStatus('error'); } else { Depager.unbindHandlers(); console.log('Error: ' + this.status); Depager.setStatus('error'); } }; Depager.onError = function() { Depager.isLoading = false; Depager.unbindHandlers(); console.log('Error: ' + this.status); Depager.setStatus('error'); }; /** * Quote inlining */ var QuoteInline = {}; QuoteInline.isSelfQuote = function(node, pid, board) { if (board && board != Main.board) { return false; } node = node.parentNode; if ((node.nodeName == 'BLOCKQUOTE' && node.id.split('m')[1] == pid) || node.parentNode.id.split('_')[1] == pid) { return true; } return false; }; QuoteInline.toggle = function(link, e) { var i, j, t, pfx, src, el, count, media; t = link.getAttribute('href').match(/(?:\/([a-z0-9]+)\/thread\/)?([0-9]+)?#p([0-9]+)$/); if (!t || QuoteInline.isSelfQuote(link, t[3], t[1])) { return; } e && e.preventDefault(); if (pfx = link.getAttribute('data-pfx')) { link.removeAttribute('data-pfx'); $.removeClass(link, 'linkfade'); el = $.id(pfx + 'p' + t[3]); media = $.cls('expandedWebm', el); for (i = 0; j = media[i]; ++i) { j.pause(); } el.parentNode.removeChild(el); if (link.parentNode.parentNode.className == 'backlink') { el = $.id('pc' + t[3]); count = +el.getAttribute('data-inline-count') - 1; if (count === 0) { el.style.display = ''; el.removeAttribute('data-inline-count'); } else { el.setAttribute('data-inline-count', count); } } return; } if (src = $.id('p' + t[3])) { QuoteInline.inline(link, src, t[3]); } else { QuoteInline.inlineRemote(link, t[1] || Main.board, t[2], t[3]); } }; QuoteInline.inlineRemote = function(link, board, tid, pid) { var onload, onerror, cached, key, el, dummy; if (link.hasAttribute('data-loading')) { return; } key = board + '-' + tid; if ((cached = $.cache[key]) && (el = Parser.buildPost(cached, board, pid))) { Parser.parsePost(el); QuoteInline.inline(link, el); return; } if ((dummy = link.nextElementSibling) && $.hasClass(dummy, 'spinner')) { dummy.parentNode.removeChild(dummy); return; } else { dummy = document.createElement('div'); } dummy.className = 'preview spinner inlined'; dummy.textContent = 'Loading...'; link.parentNode.insertBefore(dummy, link.nextSibling); onload = function() { var el, thread; link.removeAttribute('data-loading'); if (this.status === 200 || this.status === 304 || this.status === 0) { thread = Parser.parseThreadJSON(this.responseText); $.cache[key] = thread; if (el = Parser.buildPost(thread, board, pid)) { dummy.parentNode && dummy.parentNode.removeChild(dummy); Parser.parsePost(el); QuoteInline.inline(link, el); } else { $.addClass(link, 'deadlink'); dummy.textContent = 'This post doesn\'t exist anymore'; } } else if (this.status === 404) { $.addClass(link, 'deadlink'); dummy.textContent = 'This thread doesn\'t exist anymore'; } else { this.onerror(); } }; onerror = function() { dummy.textContent = 'Error: ' + this.statusText + ' (' + this.status + ')'; link.removeAttribute('data-loading'); }; link.setAttribute('data-loading', '1'); $.get('//a.4cdn.org/' + board + '/thread/' + tid + '.json', { onload: onload, onerror: onerror } ); }; QuoteInline.inline = function(link, src, id) { var i, j, now, el, blcnt, isBl, inner, tblcnt, pfx, dest, count, cnt; now = Date.now(); if (id) { if ((blcnt = link.parentNode.parentNode).className == 'backlink') { el = blcnt.parentNode.parentNode.parentNode; isBl = true; } else { el = blcnt.parentNode; } while (el.parentNode !== document) { if (el.id.split('m')[1] == id) { return; } el = el.parentNode; } } link.className += ' linkfade'; link.setAttribute('data-pfx', now); QuotePreview.stopMedia(src); el = src.cloneNode(true); el.id = now + el.id; el.setAttribute('data-pfx', now); el.className += ' preview inlined'; $.removeClass(el, 'highlight'); $.removeClass(el, 'highlight-anti'); if ((inner = $.cls('inlined', el))[0]) { while (j = inner[0]) { j.parentNode.removeChild(j); } inner = $.cls('quotelink', el); for (i = 0; j = inner[i]; ++i) { j.removeAttribute('data-pfx'); $.removeClass(j, 'linkfade'); } } for (i = 0; j = el.children[i]; ++i) { j.id = now + j.id; } if (tblcnt = $.cls('backlink', el)[0]) { tblcnt.id = now + tblcnt.id; } if (isBl) { pfx = blcnt.parentNode.parentNode.getAttribute('data-pfx') || ''; dest = $.id(pfx + 'm' + blcnt.id.split('_')[1]); dest.insertBefore(el, dest.firstChild); if (count = src.parentNode.getAttribute('data-inline-count')) { count = +count + 1; } else { count = 1; src.parentNode.style.display = 'none'; } src.parentNode.setAttribute('data-inline-count', count); } else { if ($.hasClass(link.parentNode, 'quote')) { link = link.parentNode; cnt = link.parentNode; } else { cnt = link.parentNode; } while (cnt.nodeName === 'S') { link = cnt; cnt = cnt.parentNode; } cnt.insertBefore(el, link.nextSibling); } }; /** * Quote preview */ var QuotePreview = {}; QuotePreview.init = function() { this.regex = /(?:\/([a-z0-9]+)\/thread\/)?([0-9]+)?#p([0-9]+)$/; this.highlight = null; this.highlightAnti = null; this.cur = null; }; QuotePreview.resolve = function(link) { var self, t, post, offset, pfx; self = QuotePreview; self.cur = null; t = link.getAttribute('href').match(self.regex); if (!t) { return; } // Quoted post in scope pfx = link.getAttribute('data-pfx') || ''; if (post = document.getElementById(pfx + 'p' + t[3])) { // Visible and not filtered out? offset = post.getBoundingClientRect(); if (offset.top > 0 && offset.bottom < document.documentElement.clientHeight && !$.hasClass(post.parentNode, 'post-hidden')) { if (!$.hasClass(post, 'highlight') && location.hash.slice(1) != post.id) { self.highlight = post; $.addClass(post, 'highlight'); } else if (!$.hasClass(post, 'op')) { self.highlightAnti = post; $.addClass(post, 'highlight-anti'); } return; } // Nope self.show(link, post); } // Quoted post out of scope else { if (!UA.hasCORS) { return; } self.showRemote(link, t[1] || Main.board, t[2], t[3]); } }; QuotePreview.showRemote = function(link, board, tid, pid) { var onload, onerror, el, cached, key; key = board + '-' + tid; QuotePreview.cur = key; if ((cached = $.cache[key]) && (el = Parser.buildPost(cached, board, pid))) { QuotePreview.show(link, el); return; } link.style.cursor = 'wait'; onload = function() { var el, thread; link.style.cursor = ''; if (this.status === 200 || this.status === 304 || this.status === 0) { thread = Parser.parseThreadJSON(this.responseText); $.cache[key] = thread; if ($.id('quote-preview') || QuotePreview.cur != key) { return; } if (el = Parser.buildPost(thread, board, pid)) { el.className = 'post preview'; el.style.display = 'none'; el.id = 'quote-preview'; document.body.appendChild(el); QuotePreview.show(link, el, true); } else { $.addClass(link, 'deadlink'); } } else if (this.status === 404) { $.addClass(link, 'deadlink'); } }; onerror = function() { link.style.cursor = ''; }; $.get('//a.4cdn.org/' + board + '/thread/' + tid + '.json', { onload: onload, onerror: onerror } ); }; QuotePreview.show = function(link, post, remote) { var rect, postHeight, doc, docWidth, style, pos, quotes, i, j, qid, top, scrollTop, img, media; QuotePreview.stopMedia(post); if (remote) { Parser.parsePost(post); post.style.display = ''; } else { post = post.cloneNode(true); if (location.hash && location.hash == ('#' + post.id)) { post.className += ' highlight'; } post.id = 'quote-preview'; post.className += ' preview' + (!$.hasClass(link.parentNode.parentNode, 'backlink') ? ' reveal-spoilers' : ''); if (Config.imageExpansion && (img = $.cls('expanded-thumb', post)[0])) { ImageExpansion.contract(img); } } if (media = $.cls('expandedWebm', post)[0]) { media.controls = false; media.autoplay = false; } if (!link.parentNode.className) { quotes = $.qsa( '#' + $.cls('postMessage', post)[0].id + ' > .quotelink', post ); if (quotes[1]) { qid = '>>' + link.parentNode.parentNode.id.split('_')[1]; for (i = 0; j = quotes[i]; ++i) { if (j.textContent == qid) { $.addClass(j, 'dotted'); break; } } } } rect = link.getBoundingClientRect(); doc = document.documentElement; docWidth = doc.offsetWidth; style = post.style; document.body.appendChild(post); if (Main.isMobileDevice) { style.top = rect.top + link.offsetHeight + window.pageYOffset + 'px'; if ((docWidth - rect.right) < (0 | (docWidth * 0.3))) { style.right = docWidth - rect.right + 'px'; } else { style.left = rect.left + 'px'; } } else { if ((docWidth - rect.right) < (0 | (docWidth * 0.3))) { pos = docWidth - rect.left; style.right = pos + 5 + 'px'; } else { pos = rect.left + rect.width; style.left = pos + 5 + 'px'; } top = rect.top + link.offsetHeight + window.pageYOffset - post.offsetHeight / 2 - rect.height / 2; postHeight = post.getBoundingClientRect().height; if (doc.scrollTop != document.body.scrollTop) { scrollTop = doc.scrollTop + document.body.scrollTop; } else { scrollTop = document.body.scrollTop; } if (top < scrollTop) { style.top = scrollTop + 'px'; } else if (top + postHeight > scrollTop + doc.clientHeight) { style.top = scrollTop + doc.clientHeight - postHeight + 'px'; } else { style.top = top + 'px'; } } }; QuotePreview.remove = function(el) { var self, cnt; self = QuotePreview; self.cur = null; if (self.highlight) { $.removeClass(self.highlight, 'highlight'); self.highlight = null; } else if (self.highlightAnti) { $.removeClass(self.highlightAnti, 'highlight-anti'); self.highlightAnti = null; } if (el) { el.style.cursor = ''; } if (cnt = $.id('quote-preview')) { document.body.removeChild(cnt); } }; QuotePreview.stopMedia = function(el) { var i, media; if ((media = $.tag('VIDEO', el))[0]) { for (i = 0; el = media[i]; ++i) { el.autoplay = false; } } if ((media = $.tag('AUDIO', el))[0]) { for (i = 0; el = media[i]; ++i) { el.autoplay = false; } } }; /** * Image expansion */ var ImageExpansion = { activeVideos: [], timeout: null, pendingTarget: null }; ImageExpansion.loadOgv = function(target) { ImageExpansion.pendingTarget = target; if ($.id('js-ogv-scr')) { return; } let s = $.el('script'); s.id = 'js-ogv-scr'; s.onload = ImageExpansion.onOgvLoaded; s.src = 'https://s.4cdn.org/js/ogv/ogv.js'; document.body.appendChild(s); }; ImageExpansion.onOgvLoaded = function() { let self = ImageExpansion; if (self.pendingTarget) { self.expandWebm(self.pendingTarget); } }; ImageExpansion.expand = function(thumb) { var img, href, ext, a; if (Config.imageHover) { ImageHover.hide(); } a = thumb.parentNode; href = a.getAttribute('href'); if (ext = href.match(/\.(?:webm|mp4|pdf)$/)) { if (ext[0] == '.webm' || ext[0] == '.mp4') { return ImageExpansion.expandWebm(thumb); } return false; } if (Main.hasMobileLayout && a.hasAttribute('data-m')) { href = ImageExpansion.setMobileSrc(a); } thumb.setAttribute('data-expanding', '1'); img = document.createElement('img'); img.alt = 'Image'; img.setAttribute('src', href); img.className = 'expanded-thumb'; img.style.display = 'none'; img.onerror = this.onError; thumb.parentNode.insertBefore(img, thumb.nextElementSibling); if (UA.hasCORS) { thumb.style.opacity = '0.75'; this.timeout = this.checkLoadStart(img, thumb); } else { this.onLoadStart(img, thumb); } return true; }; ImageExpansion.contract = function(img) { var cnt, p; clearTimeout(this.timeout); p = img.parentNode; cnt = p.parentNode.parentNode; $.removeClass(p.parentNode, 'image-expanded'); if (Config.centeredThreads) { $.removeClass(cnt.parentNode, 'centre-exp'); cnt.parentNode.style.marginLeft = ''; } if (!Main.tid && Config.threadHiding) { $.removeClass(p, 'image-expanded-anti'); } p.firstChild.style.display = ''; p.removeChild(img); if (cnt.offsetTop < window.pageYOffset) { cnt.scrollIntoView(); } }; ImageExpansion.toggle = function(t) { if (t.hasAttribute('data-md5')) { if (!t.hasAttribute('data-expanding')) { return ImageExpansion.expand(t); } } else { ImageExpansion.contract(t); } return true; }; ImageExpansion.setMobileSrc = function(a) { var href; a.removeAttribute('data-m'); href = a.getAttribute('href'); a.setAttribute('data-orig', href); href = href.replace(/\/([0-9]+).+$/, '/$1m.jpg'); a.setAttribute('href', href); return href; }; ImageExpansion.expandWebm = function(thumb) { var el, link, fileText, left, href, maxWidth, self, cnt; self = ImageExpansion; if (el = document.getElementById('image-hover')) { document.body.removeChild(el); } link = thumb.parentNode; href = link.getAttribute('href'); left = link.getBoundingClientRect().left; maxWidth = document.documentElement.clientWidth - left - 25; link.style.display = 'none'; if (href.match(/\.webm$/) && /iPhone|iPad/.test(navigator.userAgent)) { if (!window.OGVPlayer) { OgvCtrl.init(); self.loadOgv(thumb); return true; } if (OgvCtrl.ogv) { self.detachOgv(OgvCtrl.ogv); } cnt = document.createElement('div'); cnt.className = 'ogv-cnt expandedWebm'; el = new OGVPlayer({ wasm: true, threading: false, simd: false }); el.onloadedmetadata = self.fitWebm; el.onvolumechange = Main.getWebmVolumeChangeCb(); $.on(el, 'ended', OgvCtrl.onPlayEnd); cnt.appendChild(el); link.parentNode.appendChild(cnt); el.src = href.replace(/\/\/.+\.4chan\.org\//, '//i.4cdn.org/'); OgvCtrl.attach(el); if (!Config.unmuteWebm) { OgvCtrl.toggleMute(); } OgvCtrl.togglePlay(); } else { el = document.createElement('video'); el.muted = !Config.unmuteWebm; el.controls = true; el.loop = true; el.autoplay = true; el.className = 'expandedWebm'; el.onloadedmetadata = self.fitWebm; el.onvolumechange = Main.getWebmVolumeChangeCb(); el.onplay = self.onWebmPlay; link.parentNode.appendChild(el); el.src = href; } if (Config.unmuteWebm) { el.volume = Main.getWebmVolume(); } if (Main.hasMobileLayout) { el = document.createElement('div'); el.className = 'collapseWebm'; el.innerHTML = 'Close'; link.parentNode.appendChild(el); } else { fileText = thumb.parentNode.previousElementSibling; el = document.createElement('span'); el.className = 'collapseWebm'; el.innerHTML = '-[Close]'; fileText.appendChild(el); } $.addClass(link.parentNode, 'image-expanded'); el.firstElementChild.addEventListener('click', self.collapseWebm, false); return true; }; ImageExpansion.fitWebm = function() { var imgWidth, imgHeight, maxWidth, maxHeight, ratio, left, cntEl, centerWidth, ofs, player, target; player = this; if (OgvCtrl.ogv) { target = player.parentNode; $.addClass(target, 'ogv-loaded'); } else { target = player; } if (Config.centeredThreads) { centerWidth = $.cls('opContainer')[0].offsetWidth; cntEl = target.parentNode.parentNode.parentNode; $.addClass(cntEl, 'centre-exp'); } left = player.getBoundingClientRect().left; maxWidth = document.documentElement.clientWidth - left; maxHeight = document.documentElement.clientHeight; if (!Main.hasMobileLayout) { maxWidth -= 25; } imgWidth = player.videoWidth; imgHeight = player.videoHeight; if (imgWidth > maxWidth) { ratio = maxWidth / imgWidth; imgWidth = maxWidth; imgHeight = imgHeight * ratio; } if (Config.fitToScreenExpansion && imgHeight > maxHeight) { ratio = maxHeight / imgHeight; imgHeight = maxHeight; imgWidth = imgWidth * ratio; } target.style.width = (0 | imgWidth) + 'px'; target.style.height = (0 | imgHeight) + 'px'; if (player !== target) { player.style.width = target.style.width; player.style.height = target.style.height; } if (Config.centeredThreads) { left = target.getBoundingClientRect().left; ofs = target.offsetWidth + left * 2; if (ofs > centerWidth) { left = Math.floor(($.docEl.clientWidth - ofs) / 2); if (left > 0) { cntEl.style.marginLeft = left + 'px'; } } else { $.removeClass(cntEl, 'centre-exp'); } } }; ImageExpansion.onWebmPlay = function() { var self = ImageExpansion; if (!self.activeVideos.length) { document.addEventListener('scroll', self.onScroll, false); } self.activeVideos.push(this); }; ImageExpansion.collapseWebm = function(e) { var cnt, el, el2; e.preventDefault(); this.removeEventListener('click', ImageExpansion.collapseWebm, false); cnt = this.parentNode; $.removeClass(cnt.parentNode, 'image-expanded'); if (Main.hasMobileLayout) { el = cnt.previousElementSibling; } else { el = cnt.parentNode.parentNode.getElementsByClassName('expandedWebm')[0]; } if (el.classList.contains('ogv-cnt')) { if (!el.classList.contains('ogv-detached')) { el.firstElementChild.stop(); OgvCtrl.detach(); } } else { el.pause(); } if (Config.centeredThreads) { el2 = el.parentNode.parentNode.parentNode; $.removeClass(el2, 'centre-exp'); el2.style.marginLeft = ''; } el.previousElementSibling.style.display = ''; el.parentNode.removeChild(el); cnt.parentNode.removeChild(cnt); }; ImageExpansion.detachOgv = function(ogv) { let cnt = ogv.parentNode; cnt.style.width = ogv.style.width; cnt.style.height = ogv.style.height; cnt.classList.add('ogv-detached'); ogv.remove(); }; ImageExpansion.onScroll = function() { clearTimeout(ImageExpansion.timeout); ImageExpansion.timeout = setTimeout(ImageExpansion.pauseVideos, 500); }; ImageExpansion.pauseVideos = function() { var self, i, el, pos, min, max, nodes; self = ImageExpansion; nodes = []; min = window.pageYOffset; max = window.pageYOffset + $.docEl.clientHeight; for (i = 0; el = self.activeVideos[i]; ++i) { pos = el.getBoundingClientRect(); if (pos.top + window.pageYOffset > max || pos.bottom + window.pageYOffset < min) { el.pause(); } else if (!el.paused){ nodes.push(el); } } if (!nodes.length) { document.removeEventListener('scroll', self.onScroll, false); } self.activeVideos = nodes; }; ImageExpansion.onError = function(e) { var thumb, img; Feedback.error('File no longer exists (404).', 2000); img = e.target; thumb = $.qs('img[data-expanding]', img.parentNode); img.parentNode.removeChild(img); thumb.style.opacity = ''; thumb.removeAttribute('data-expanding'); }; ImageExpansion.onLoadStart = function(img, thumb) { var imgWidth, imgHeight, maxWidth, maxHeight, ratio, left, fileEl, cntEl, centerWidth, ofs, el; thumb.removeAttribute('data-expanding'); fileEl = thumb.parentNode.parentNode; if (Config.centeredThreads) { cntEl = fileEl.parentNode.parentNode; centerWidth = $.cls('opContainer')[0].offsetWidth; $.addClass(cntEl, 'centre-exp'); } left = thumb.getBoundingClientRect().left; maxWidth = $.docEl.clientWidth - left - 25; maxHeight = $.docEl.clientHeight; imgWidth = img.naturalWidth; imgHeight = img.naturalHeight; if (imgWidth > maxWidth) { ratio = maxWidth / imgWidth; imgWidth = maxWidth; imgHeight = imgHeight * ratio; } if (Config.fitToScreenExpansion && imgHeight > maxHeight) { ratio = maxHeight / imgHeight; imgHeight = maxHeight; imgWidth = imgWidth * ratio; } img.style.maxWidth = imgWidth + 'px'; img.style.maxHeight = imgHeight + 'px'; $.addClass(fileEl, 'image-expanded'); if (!Main.tid && Config.threadHiding) { $.addClass(thumb.parentNode, 'image-expanded-anti'); } img.style.display = ''; thumb.style.display = 'none'; if (Config.centeredThreads) { left = img.getBoundingClientRect().left; ofs = img.offsetWidth + left * 2; if (ofs > centerWidth) { left = Math.floor(($.docEl.clientWidth - ofs) / 2); if (left > 0) { cntEl.style.marginLeft = left + 'px'; } } else { $.removeClass(cntEl, 'centre-exp'); } } else if (Main.hasMobileLayout) { cntEl = thumb.parentNode.lastElementChild; if (!cntEl.firstElementChild) { fileEl = document.createElement('div'); fileEl.className = 'mFileName'; if (el = thumb.parentNode.parentNode.getElementsByClassName('fileText')[0]) { el = el.firstElementChild; fileEl.innerHTML = el.getAttribute('title') || el.innerHTML; } cntEl.insertBefore(fileEl, cntEl.firstChild); } } }; ImageExpansion.checkLoadStart = function(img, thumb) { if (img.naturalWidth) { ImageExpansion.onLoadStart(img, thumb); thumb.style.opacity = ''; } else { return setTimeout(ImageExpansion.checkLoadStart, 15, img, thumb); } }; /** * Image hover */ var ImageHover = {}; ImageHover.show = function(thumb) { var el, href, ext; if (thumb.nodeName !== 'A') { href = thumb.parentNode.getAttribute('href'); } else { href = thumb.getAttribute('href'); } if (ext = href.match(/\.(?:webm|mp4|pdf)$/)) { if (ext[0] == '.webm' || ext[0] == '.mp4') { ImageHover.showWebm(thumb); } return; } el = document.createElement('img'); el.id = 'image-hover'; el.alt = 'Image'; el.onerror = ImageHover.onLoadError; el.src = href; if (Config.imageHoverBg) { el.style.backgroundColor = 'inherit'; } document.body.appendChild(el); if (UA.hasCORS) { el.style.display = 'none'; this.timeout = ImageHover.checkLoadStart(el, thumb); } else { el.style.left = thumb.getBoundingClientRect().right + 10 + 'px'; } }; ImageHover.hide = function() { var img; clearTimeout(this.timeout); if (img = $.id('image-hover')) { if (img.play) { img.pause(); Tip.hide(); } document.body.removeChild(img); } }; ImageHover.showWebm = function(thumb) { var el; el = document.createElement('video'); el.id = 'image-hover'; if (Config.imageHoverBg) { el.style.backgroundColor = 'inherit'; } if (thumb.nodeName !== 'A') { el.src = thumb.parentNode.getAttribute('href'); } else { el.src = thumb.getAttribute('href'); } el.loop = true; el.muted = !Config.unmuteWebm; el.autoplay = true; el.onerror = ImageHover.onLoadError; el.onloadedmetadata = function() { ImageHover.showWebMDuration(this, thumb); }; el.onvolumechange = Main.getWebmVolumeChangeCb(); document.body.appendChild(el); if (Config.unmuteWebm) { el.volume = Main.getWebmVolume(); } }; ImageHover.showWebMDuration = function(el, thumb) { var w, h, aabb; if (!el.parentNode) { return; } var sound, ms = $.prettySeconds(el.duration); if (el.mozHasAudio === true || el.webkitAudioDecodedByteCount > 0 || (el.audioTracks && el.audioTracks.length)) { sound = ' (audio)'; } else { sound = ''; } aabb = thumb.getBoundingClientRect(); [w, h] = $.fit(el.videoWidth, el.videoHeight, window.innerWidth - aabb.right - 20, window.innerHeight); el.style.width = w + 'px'; el.style.height = h + 'px'; Tip.show(thumb, ms[0] + ':' + ('0' + ms[1]).slice(-2) + sound); }; ImageHover.onLoadError = function() { Feedback.error('File no longer exists (404).', 2000); }; ImageHover.onLoadStart = function(img, thumb) { var bounds, limit; bounds = thumb.getBoundingClientRect(); limit = window.innerWidth - bounds.right - 20; if (img.naturalWidth > limit) { img.style.maxWidth = limit + 'px'; } img.style.display = ''; }; ImageHover.checkLoadStart = function(img, thumb) { if (img.naturalWidth) { ImageHover.onLoadStart(img, thumb); } else { return setTimeout(ImageHover.checkLoadStart, 15, img, thumb); } }; /** * Quick reply */ var QR = {}; QR.init = function() { var item; if (!UA.hasFormData) { return; } this.enabled = !!document.forms.post; this.currentTid = null; this.cooldown = null; this.timestamp = null; this.auto = false; this.btn = null; this.comField = null; this.comLength = window.comlen; this.comCheckTimeout = null; this.cdElapsed = 0; this.activeDelay = 0; this.ctTTL = 290; this.ctTimeout = null; this.cooldowns = {}; for (item in window.cooldowns) { this.cooldowns[item] = window.cooldowns[item] * 1000; } if (this.noCaptcha) { for (item in this.cooldowns) { this.cooldowns[item] = Math.ceil(this.cooldowns[item] / 2); } } this.painterTime = 0; this.painterData = null; this.painterSrc = null; this.replayBlob = null; this.canvasLoading = false; this.captchaWidgetCnt = null; this.captchaWidgetId = null; this.pulse = null; this.xhr = null; this.fileDisabled = !!window.imagelimit; this.tracked = {}; if (Main.tid && !Main.hasMobileLayout && !Main.threadClosed) { QR.addReplyLink(); } window.addEventListener('storage', this.syncStorage, false); }; QR.openTeXPreview = function() { var el; QR.closeTeXPreview(); if (!window.MathJax) { window.loadMathJax(); } el = document.createElement('div'); el.id = 'tex-preview-cnt'; el.className = 'UIPanel'; el.setAttribute('data-cmd', 'close-tex-preview'); el.innerHTML = '\
Preview\ Close
Use [math][/math] tags for inline, and [eqn][/eqn] tags for block equations.
\
'; document.body.appendChild(el); el = $.id('input-tex-preview'); $.on(el, 'keyup', QR.onTeXChanged); }; QR.closeTeXPreview = function() { var el; if (el = $.id('input-tex-preview')) { $.off(el, 'keyup', QR.onTeXChanged); el = $.id('tex-preview-cnt'); el.parentNode.removeChild(el); } }; QR.onTeXChanged = function() { clearTimeout(QR.timeoutTeX); QR.timeoutTeX = setTimeout(QR.processTeX, 50); }; QR.processTeX = function() { var src, dest; if (QR.processingTeX || !window.MathJax || !(src = $.id('input-tex-preview'))) { return; } dest = $.id('output-tex-preview'); dest.textContent = src.value; QR.processingTeX = true; MathJax.Hub.Queue(['Typeset', MathJax.Hub, dest], ['onTeXReady', QR]); }; QR.onTeXReady = function() { QR.processingTeX = false; }; QR.validateCT = function() { var ct, now, d; if (!QR.captchaWidgetCnt) { return; } ct = Main.getCookie('_ct'); if (!ct) { if (QR.ctTimeout) { QR.setCTTag(); } return; } if (QR.ctTimeout) { return; } ct = ct.split('.')[1]; now = 0 | (Date.now() / 1000); d = now - ct; if (d >= QR.ctTTL) { QR.setCTTag(); } else { QR.setCTTag(QR.ctTTL - d); } }; QR.setCTTag = function(sec) { if (window.t_captcha) { return; } var el = QR.captchaWidgetCnt; QR.clearCTTimeout(); if (sec && sec > 0) { QR.ctTimeout = setTimeout(QR.setCTTag, sec * 1000); el.style.opacity = '0.25'; $.on(el, 'mouseover', QR.onCTMouseOver); $.on(el, 'mouseout', QR.onCTMouseOut); } else { el.style.opacity = ''; $.off(el, 'mouseover', QR.onCTMouseOver); $.off(el, 'mouseout', QR.onCTMouseOut); } }; QR.onCTMouseOver = function() { QR.onCTMouseOut(); QR.ctTipTimeout = setTimeout(Tip.show, Tip.delay, QR.captchaWidgetCnt, 'Verification not required for your next post.'); }; QR.onCTMouseOut = function() { clearTimeout(QR.ctTipTimeout); Tip.hide(); }; QR.clearCTTimeout = function() { clearTimeout(QR.ctTimeout); QR.ctTimeout = null; }; QR.addReplyLink = function() { var cnt, el; cnt = $.cls('navLinks')[2]; el = document.createElement('div'); el.className = 'open-qr-wrap'; el.innerHTML = '[Post a Reply]'; cnt.insertBefore(el, cnt.firstChild); }; QR.lock = function() { QR.showPostError('This thread is closed.', 'closed', true); }; QR.unlock = function() { QR.hidePostError('closed'); }; QR.syncStorage = function(e) { var key; if (!e.key) { return; } key = e.key.split('-'); if (key[0] != '4chan') { return; } if (key[1] == 'cd' && e.newValue && Main.board == key[2]) { QR.startCooldown(); } }; QR.onOpenInPainterClick = function(btn) { var img, el, tid, pid; if (QR.canvasLoading) { Feedback.error('An image is already being loaded.'); return; } pid = +btn.getAttribute('data-pid'); tid = +btn.getAttribute('data-tid'); if (!pid || !tid) { return false; } el = $.qs('#f' + pid + ' a[class="fileThumb"]'); if (!el) { return false; } if (/\.(png|jpg)$/.test(el.href) === false) { return false; } QR.canvasLoading = true; img = new Image(); img.crossOrigin = 'Anonymous'; img.onload = QR.onPainterCanvasLoaded; img.onerror = QR.onPainterCanvasError; img._pid = pid; Feedback.notify('Loading…', 0); img.src = el.href.replace('is2.4chan.org', 'i.4cdn.org'); QR.show(tid); }; QR.onPainterCanvasError = function() { QR.canvasLoading = false; Feedback.error("Couldn't load the image.", 5000); }; QR.onPainterCanvasLoaded = function() { Feedback.hideMessage(); QR.canvasLoading = false; if (!QR.currentTid) { return; } if (this.naturalWidth < 1 || this.naturalHeight < 1) { return; } if (Tegaki.startTimeStamp) { Tegaki.destroy(); } Keybinds.enabled = false; QR.painterSrc = this._pid; Tegaki.open({ onDone: QR.onPainterDone, onCancel: QR.onPainterCancel, saveReplay: false, width: 1, height: 1 }); Tegaki.onOpenImageLoaded.call(this); }; QR.openPainter = function() { var w, h, dims, cb; dims = $.tag('input', $.id('qr-painter-ctrl')); w = +dims[0].value; h = +dims[1].value; if (w < 1 || h < 1) { return; } cb = $.cls('oe-r-cb', $.id('qr-painter-ctrl'))[0]; Keybinds.enabled = false; window.Tegaki.open({ onDone: QR.onPainterDone, onCancel: QR.onPainterCancel, saveReplay: cb && cb.checked, width: w, height: h }); }; QR.onPainterDone = function() { var el, cnt; Keybinds.enabled = true; QR.painterData = Tegaki.flatten().toDataURL('image/png'); if (Tegaki.saveReplay) { QR.replayBlob = Tegaki.replayRecorder.toBlob(); } QR.painterTime = 0; if (Tegaki.startTimeStamp) { if (!Tegaki.hasCustomCanvas || QR.painterSrc) { QR.painterTime = Math.round((Date.now() - Tegaki.startTimeStamp) / 1000); } } if (el = $.id('qrFile')) { el.style.visibility = 'hidden'; } cnt = $.id('qr-painter-ctrl'); if (el = $.tag('button', cnt)[0]) { el.textContent = 'Edit'; } if (el = $.tag('button', cnt)[1]) { el.disabled = false; } for (el of $.tag('input', cnt)) { el.disabled = true; } }; QR.onPainterCancel = function() { var el, cnt; Keybinds.enabled = true; QR.painterData = null; QR.painterSrc = null; QR.replayBlob = null; QR.painterTime = 0; if (el = $.id('qrFile')) { el.style.visibility = ''; } cnt = $.id('qr-painter-ctrl'); if (el = $.tag('button', cnt)[0]) { el.textContent = 'Draw'; } if (el = $.tag('button', cnt)[1]) { el.disabled = true; } for (el of $.tag('input', cnt)) { el.disabled = false; } }; QR.quotePost = function(tid, pid) { if (!QR.noCooldown && (Main.threadClosed || (!Main.tid && Main.isThreadClosed(tid)))) { alert('This thread is closed'); return; } QR.show(tid); QR.addQuote(pid); }; QR.addQuote = function(pid) { var q, pos, sel, ta; ta = $.tag('textarea', document.forms.qrPost)[0]; pos = ta.selectionStart; sel = UA.getSelection(); if (pid) { q = '>>' + pid + '\n'; } else { q = ''; } if (sel) { q += '>' + sel.trim().replace(/[\r\n]+/g, '\n>') + '\n'; } if (ta.value) { ta.value = ta.value.slice(0, pos) + q + ta.value.slice(ta.selectionEnd); } else { ta.value = q; } if (UA.isOpera) { pos += q.split('\n').length; } ta.selectionStart = ta.selectionEnd = pos + q.length; if (ta.selectionStart == ta.value.length) { ta.scrollTop = ta.scrollHeight; } ta.focus(); }; QR.show = function(tid) { var i, j, cnt, postForm, form, qrForm, fields, row, spoiler, file, el, el2, placeholder, qrError, cookie, mfs; window.checkIncognito && window.checkIncognito(); if (QR.currentTid) { if (!Main.tid && QR.currentTid != tid) { $.id('qrTid').textContent = $.id('qrResto').value = QR.currentTid = tid; $.byName('com')[1].value = ''; QR.startCooldown(); } if (Main.hasMobileLayout) { $.id('quickReply').style.top = window.pageYOffset + 25 + 'px'; } return; } QR.currentTid = tid; postForm = $.id('postForm'); cnt = document.createElement('div'); cnt.id = 'quickReply'; cnt.className = 'extPanel reply'; cnt.setAttribute('data-trackpos', 'QR-position'); if (Main.hasMobileLayout) { cnt.style.top = window.pageYOffset + 28 + 'px'; } else if (Config['QR-position']) { cnt.style.cssText = Config['QR-position']; } else { cnt.style.right = '0px'; cnt.style.top = '10%'; } cnt.innerHTML = '
' + (window.math_tags ? '' : '') + 'Reply to Thread No.' + tid + 'X
'; form = postForm.parentNode.cloneNode(false); form.setAttribute('name', 'qrPost'); form.innerHTML = ( (mfs = $.byName('MAX_FILE_SIZE')[0]) ? ('') : '' ) + '' + ''; qrForm = document.createElement('div'); qrForm.id = 'qrForm'; this.btn = null; fields = postForm.firstElementChild.children; for (i = 0, j = fields.length - 1; i < j; ++i) { row = document.createElement('div'); if (fields[i].id == 'captchaFormPart') { if (QR.noCaptcha) { continue; } row.id = 'qrCaptchaContainer'; row.classList.add('t-qr-root'); QR.captchaWidgetCnt = row; } else { placeholder = fields[i].getAttribute('data-type'); if (placeholder == 'Password' || placeholder == 'Spoilers') { continue; } else if (placeholder == 'File') { file = fields[i].children[1].firstChild.cloneNode(false); file.tabIndex = ''; file.id = 'qrFile'; file.size = '19'; file.addEventListener('change', QR.onFileChange, false); row.appendChild(file); file.title = 'Shift + Click to remove the file'; } else if (placeholder == 'Painter') { row.innerHTML = fields[i].children[1].innerHTML; row.id = 'qr-painter-ctrl'; row.className = 'desktop'; el = row.getElementsByTagName('button'); el[0].setAttribute('data-cmd', 'qr-painter-draw'); el[1].setAttribute('data-cmd', 'qr-painter-clear'); } else { row.innerHTML = fields[i].children[1].innerHTML; if (row.firstChild.type == 'hidden') { el = row.lastChild.previousSibling; } else { el = row.firstChild; } el.tabIndex = ''; if (el.nodeName == 'INPUT' || el.nodeName == 'TEXTAREA') { if (el.name == 'name') { if (cookie = Main.getCookie('4chan_name')) { el.value = cookie; } } else if (el.name == 'email') { el.id = 'qrEmail'; if (cookie = Main.getCookie('options')) { el.value = cookie; } if (el.nextElementSibling) { el.parentNode.removeChild(el.nextElementSibling); } } else if (el.name == 'com') { QR.comField = el; el.addEventListener('keydown', QR.onKeyDown, false); el.addEventListener('paste', QR.onKeyDown, false); el.addEventListener('cut', QR.onKeyDown, false); if (row.children[1]) { row.removeChild(el.nextSibling); } } else if (el.name == 'sub') { continue; } if (placeholder !== null) { el.setAttribute('placeholder', placeholder); } } else if ((el.name == 'flag')) { if (el2 = $.qs('option[selected]', el)) { el2.removeAttribute('selected'); } if ((cookie = localStorage.getItem('4chan_flag_' + Main.board)) && (el2 = $.qs('option[value="' + cookie + '"]', el))) { el2.setAttribute('selected', 'selected'); } $.on(el, 'change', window.onBoardFlagChanged); } } } qrForm.appendChild(row); } if (!this.btn) { this.btn = $.qs('input[type="submit"]', postForm).cloneNode(false); this.btn.tabIndex = ''; if (file) { file.parentNode.appendChild(this.btn); } else { qrForm.appendChild(document.createElement('div')); qrForm.lastElementChild.appendChild(this.btn); } } if (el = $.qs('.desktop > label > input[name="spoiler"]', postForm)) { spoiler = document.createElement('span'); spoiler.id = 'qrSpoiler'; spoiler.innerHTML = ''; file.parentNode.insertBefore(spoiler, file.nextSibling); } form.appendChild(qrForm); cnt.appendChild(form); qrError = document.createElement('div'); qrError.id = 'qrError'; cnt.appendChild(qrError); cnt.addEventListener('click', QR.onClick, false); document.body.appendChild(cnt); QR.startCooldown(); if (Main.threadClosed) { QR.lock(); } if (!window.passEnabled) { QR.renderCaptcha(); } if (!Main.hasMobileLayout) { Draggable.set($.id('qrHeader')); } }; QR.renderCaptcha = function() { if (window.grecaptcha) { QR.captchaWidgetId = grecaptcha.render(QR.captchaWidgetCnt, { sitekey: window.recaptchaKey, theme: (Main.stylesheet === 'tomorrow' || window.dark_captcha) ? 'dark' : 'light' }); QR.validateCT(); return; } else if (window.t_captcha) { TCaptcha.init(QR.captchaWidgetCnt, Main.board, QR.currentTid); TCaptcha.setErrorCb(QR.showPostError); } }; QR.resetCaptcha = function() { if (window.grecaptcha) { if (QR.captchaWidgetId !== null) { grecaptcha.reset(QR.captchaWidgetId); QR.validateCT(); } } else if (window.t_captcha) { TCaptcha.clearChallenge(); } }; QR.onPassError = function() { var el, cnt; if (QR.captchaWidgetCnt) { return; } window.passEnabled = QR.noCaptcha = false; el = document.createElement('div'); el.id = 'qrCaptchaContainer'; cnt = $.id('qrForm'); cnt.insertBefore(el, cnt.lastElementChild); QR.captchaWidgetCnt = el; QR.renderCaptcha(); }; QR.onFileChange = function() { var fsize, maxFilesize; if (this.value) { maxFilesize = window.maxFilesize; if (this.files) { fsize = this.files[0].size; if (this.files[0].type == 'video/webm' && window.maxWebmFilesize) { maxFilesize = window.maxWebmFilesize; } } else { fsize = 0; } if (QR.fileDisabled) { QR.showPostError('Image limit reached.', 'imagelimit', true); } else if (fsize > maxFilesize) { QR.showPostError('Error: Maximum file size allowed is ' + Math.floor(maxFilesize / 1048576) + ' MB', 'filesize', true); } else { QR.hidePostError(); } } else { QR.hidePostError(); } QR.painterData = null; QR.replayBlob = null; QR.startCooldown(); }; QR.onKeyDown = function(e) { if (e.ctrlKey && e.keyCode == 83) { var ta, start, end, spoiler; e.stopPropagation(); e.preventDefault(); ta = e.target; start = ta.selectionStart; end = ta.selectionEnd; if (ta.value) { spoiler = '[spoiler]' + ta.value.slice(start, end) + '[/spoiler]'; ta.value = ta.value.slice(0, start) + spoiler + ta.value.slice(end); ta.setSelectionRange(end + 19, end + 19); } else { ta.value = '[spoiler][/spoiler]'; ta.setSelectionRange(9, 9); } } else if (e.keyCode == 27 && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { QR.close(); return; } clearTimeout(QR.comCheckTimeout); QR.comCheckTimeout = setTimeout(QR.checkCommentField, 500); }; QR.checkCommentField = function() { var byteLength; if (QR.comLength) { byteLength = encodeURIComponent(QR.comField.value).split(/%..|./).length - 1; if (byteLength > QR.comLength) { QR.showPostError('Error: Comment too long (' + byteLength + '/' + QR.comLength + ').', 'length', true); } else { QR.hidePostError('length'); } } if (window.sjis_tags) { if (/\[sjis\]/.test(QR.comField.value)) { if (!$.hasClass(QR.comField, 'sjis')) { $.addClass(QR.comField, 'sjis'); } } else { if ($.hasClass(QR.comField, 'sjis')) { $.removeClass(QR.comField, 'sjis'); } } } }; QR.close = function() { var el, cnt = $.id('quickReply'); QR.comField = null; QR.currentTid = null; QR.painterTime = 0; QR.painterData = null; QR.painterSrc = null; QR.replayBlob = null; QR.canvasLoading = false; clearInterval(QR.pulse); if (QR.xhr) { QR.xhr.abort(); QR.xhr = null; } cnt.removeEventListener('click', QR.onClick, false); (el = $.id('qrFile')) && el.removeEventListener('change', QR.startCooldown, false); (el = $.id('qrEmail')) && el.removeEventListener('change', QR.startCooldown, false); $.tag('textarea', cnt)[0].removeEventListener('keydown', QR.onKeyDown, false); Draggable.unset($.id('qrHeader')); if (!QR.noCaptcha) { QR.setCTTag(); } if (window.t_captcha && TCaptcha.node === QR.captchaWidgetCnt) { TCaptcha.destroy(); } document.body.removeChild(cnt); }; QR.onClick = function(e) { var t = e.target; if (t.type == 'submit') { e.preventDefault(); QR.submit(e.shiftKey); } else { switch (t.id) { case 'qrFile': if (e.shiftKey) { e.preventDefault(); QR.resetFile(); } break; case 'qrDummyFile': case 'qrDummyFileButton': case 'qrDummyFileLabel': e.preventDefault(); if (e.shiftKey) { QR.resetFile(); } else { $.id('qrFile').click(); } break; case 'qrClose': QR.close(); break; } } }; QR.showPostError = function(msg, type, silent) { var qrError; qrError = $.id('qrError'); if (!qrError) { return; } if (!msg) { QR.hidePostError(); return; } qrError.innerHTML = msg; qrError.style.display = 'block'; qrError.setAttribute('data-type', type || ''); if (!silent && (document.hidden || document.mozHidden || document.webkitHidden || document.msHidden)) { alert('Posting Error'); } }; QR.hidePostError = function(type) { var el = $.id('qrError'); if (!el.hasAttribute('style')) { return; } if (!type || el.getAttribute('data-type') == type) { el.removeAttribute('style'); } }; QR.resetFile = function() { var file, el; QR.painterData = null; QR.replayBlob = null; el = document.createElement('input'); el.id = 'qrFile'; el.type = 'file'; el.size = '19'; el.name = 'upfile'; el.addEventListener('change', QR.onFileChange, false); file = $.id('qrFile'); file.removeEventListener('change', QR.onFileChange, false); file.parentNode.replaceChild(el, file); QR.hidePostError('imagelimit'); QR.needPreuploadCaptcha = false; QR.startCooldown(); }; QR.submit = function(force) { var formdata; QR.hidePostError(); if (!QR.presubmitChecks(force)) { return; } QR.auto = false; QR.xhr = new XMLHttpRequest(); QR.xhr.open('POST', document.forms.qrPost.action, true); QR.xhr.withCredentials = true; QR.xhr.setRequestHeader('Accept', 'application/json'); QR.xhr.upload.onprogress = function(e) { if (e.loaded >= e.total) { QR.btn.value = '100%'; } else { QR.btn.value = (0 | (e.loaded / e.total * 100)) + '%'; } }; QR.xhr.onerror = function() { QR.xhr = null; QR.showPostError('Connection error.'); }; QR.xhr.onload = function() { var resp, el, hasFile, ids, tid, pid, tracked; QR.xhr = null; QR.btn.value = 'Post'; if (this.status == 200) { if (this.getResponseHeader("Content-Type") == 'application/json') { try { resp = JSON.parse(this.responseText); } catch (e) { console.log('QR resp', e); resp = { error: 'Something went wrong.' }; } if (resp.error) { if (window.passEnabled && /4chan Pass/.test(resp.error)) { QR.onPassError(); } else { QR.resetCaptcha(); } QR.showPostError(resp.error); return; } if (resp.pid) { tid = resp.tid; pid = resp.pid; } } else if (resp = this.responseText.match(/"errmsg"[^>]*>(.*?)<\/span/)) { if (window.passEnabled && /4chan Pass/.test(resp)) { QR.onPassError(); } else { QR.resetCaptcha(); } QR.showPostError(resp[1]); return; } else if (ids = this.responseText.match(//)) { tid = ids[1]; pid = ids[2]; } if (pid) { hasFile = (el = $.id('qrFile')) && el.value; QR.setPostTime(); if (Config.persistentQR) { $.byName('com')[1].value = ''; if (el = $.byName('spoiler')[2]) { el.checked = false; } QR.resetCaptcha(); if (hasFile || QR.painterData) { QR.resetFile(); } QR.startCooldown(); } else { QR.close(); } if (Main.tid) { if (Config.threadWatcher) { ThreadWatcher.setLastRead(pid, tid); } QR.lastReplyId = +pid; Parser.trackedReplies['>>' + pid] = 1; Parser.saveTrackedReplies(tid, Parser.trackedReplies); } else { tracked = Parser.getTrackedReplies(Main.board, tid) || {}; tracked['>>' + pid] = 1; Parser.saveTrackedReplies(tid, tracked); } Parser.touchTrackedReplies(tid); UA.dispatchEvent('4chanQRPostSuccess', { threadId: tid, postId: pid }); } if (ThreadUpdater.enabled) { setTimeout(ThreadUpdater.forceUpdate, 500); } } else { QR.showPostError('Error: ' + this.status + ' ' + this.statusText); } }; formdata = new FormData(document.forms.qrPost); if (formdata.entries && formdata.delete && (!formdata.get('upfile') || !formdata.get('upfile').size)) { formdata.delete('upfile'); } if (QR.painterData) { QR.appendPainter(formdata); if (QR.replayBlob) { formdata.append('oe_replay', QR.replayBlob, 'tegaki.tgkr'); } formdata.append('oe_time', QR.painterTime); if (QR.painterSrc) { formdata.append('oe_src', QR.painterSrc); } } clearInterval(QR.pulse); QR.btn.value = 'Sending'; QR.xhr.send(formdata); }; QR.appendPainter = function(formdata) { var blob; blob = QR.b64toBlob(QR.painterData.slice(QR.painterData.indexOf(',') + 1)); if (blob) { if (blob.size > window.maxFilesize) { QR.showPostError('Error: Maximum file size allowed is ' + Math.floor(window.maxFilesize / 1048576) + ' MB', 'filesize', true); return; } formdata.append('upfile', blob, 'tegaki.png'); } }; QR.b64toBlob = function(data) { var i, bytes, ary, bary, len; bytes = atob(data); len = bytes.length; ary = new Array(len); for (i = 0; i < len; ++i) { ary[i] = bytes.charCodeAt(i); } bary = new Uint8Array(ary); return new Blob([bary]); }; QR.presubmitChecks = function(force) { if (QR.xhr) { QR.xhr.abort(); QR.xhr = null; QR.showPostError('Aborted.'); QR.btn.value = 'Post'; return false; } if (!force && QR.cooldown) { if (QR.auto = !QR.auto) { QR.btn.value = QR.cooldown + 's (auto)'; } else { QR.btn.value = QR.cooldown + 's'; } return false; } if (window.isIncognito) { let el = $.id('qrFile'); if (el && el.value && !QR.painterData) { QR.showPostError('Uploading files in incognito mode is not allowed.' + '
The File field has been cleared.'); QR.resetFile(); return false; } } return true; }; QR.getCooldown = function(type) { return QR.cooldowns[type]; }; QR.setPostTime = function() { return localStorage.setItem('4chan-cd-' + Main.board, Date.now()); }; QR.getPostTime = function() { return localStorage.getItem('4chan-cd-' + Main.board); }; QR.removePostTime = function() { return localStorage.removeItem('4chan-cd-' + Main.board); }; QR.startCooldown = function() { var type, el, time; if (QR.noCooldown || !$.id('quickReply') || QR.xhr) { return; } clearInterval(QR.pulse); type = ((el = $.id('qrFile')) && el.value) ? 'image' : 'reply'; time = QR.getPostTime(type); if (!time) { QR.btn.value = 'Post'; return; } QR.timestamp = parseInt(time, 10); QR.activeDelay = QR.getCooldown(type); QR.cdElapsed = Date.now() - QR.timestamp; QR.cooldown = Math.ceil((QR.activeDelay - QR.cdElapsed) / 1000); if (QR.cooldown <= 0 || QR.cdElapsed < 0) { QR.cooldown = false; QR.btn.value = 'Post'; return; } QR.btn.value = QR.cooldown + 's'; QR.pulse = setInterval(QR.onPulse, 1000); }; QR.onPulse = function() { QR.cdElapsed = Date.now() - QR.timestamp; QR.cooldown = Math.ceil((QR.activeDelay - QR.cdElapsed) / 1000); if (QR.cooldown <= 0) { clearInterval(QR.pulse); QR.btn.value = 'Post'; QR.cooldown = false; if (QR.auto) { QR.submit(); } } else { QR.btn.value = QR.cooldown + (QR.auto ? 's (auto)' : 's'); } }; /** * Thread hiding */ var ThreadHiding = {}; ThreadHiding.init = function() { this.threshold = 43200000; // 12 hours this.hidden = {}; this.load(); this.purge(); }; ThreadHiding.clear = function(silent) { var i, id, key, msg; this.load(); i = 0; for (id in this.hidden) { ++i; } key = '4chan-hide-t-' + Main.board; if (!silent) { if (!i) { alert("You don't have any hidden threads on /" + Main.board + '/'); return; } msg = 'This will unhide ' + i + ' thread' + (i > 1 ? 's' : '') + ' on /' + Main.board + '/'; if (!confirm(msg)) { return; } localStorage.removeItem(key); } else { localStorage.removeItem(key); } }; ThreadHiding.isHidden = function(tid) { return !!ThreadHiding.hidden[tid]; }; ThreadHiding.toggle = function(tid) { if (this.isHidden(tid)) { this.show(tid); } else { this.hide(tid); } this.save(); }; ThreadHiding.show = function(tid) { var sa, th; th = $.id('t' + tid); sa = $.id('sa' + tid); if (Main.hasMobileLayout) { sa.parentNode.removeChild(sa); th.style.display = null; $.removeClass(th.nextElementSibling, 'mobile-hr-hidden'); } else { sa.removeAttribute('data-hidden'); sa.firstChild.src = Main.icons.minus; $.removeClass(th, 'post-hidden'); } delete this.hidden[tid]; }; ThreadHiding.hide = function(tid) { var sa, th; th = $.id('t' + tid); if (Main.hasMobileLayout) { th.style.display = 'none'; $.addClass(th.nextElementSibling, 'mobile-hr-hidden'); sa = document.createElement('span'); sa.id = 'sa' + tid; sa.setAttribute('data-cmd', 'hide'); sa.setAttribute('data-id', tid); sa.textContent = 'Show Hidden Thread'; sa.className = 'mobileHideButton button mobile-tu-show'; th.parentNode.insertBefore(sa, th); } else { if (Config.hideStubs && !$.cls('stickyIcon', th)[0]) { th.style.display = th.nextElementSibling.style.display = 'none'; } else { sa = $.id('sa' + tid); sa.setAttribute('data-hidden', tid); sa.firstChild.src = Main.icons.plus; th.className += ' post-hidden'; } } this.hidden[tid] = Date.now(); }; ThreadHiding.load = function() { var storage; if (storage = localStorage.getItem('4chan-hide-t-' + Main.board)) { this.hidden = JSON.parse(storage); } }; ThreadHiding.purge = function() { var i, hasHidden, lastPurged, key; key = '4chan-purge-t-' + Main.board; lastPurged = localStorage.getItem(key); for (i in this.hidden) { hasHidden = true; break; } if (!hasHidden) { return; } if (!lastPurged || lastPurged < Date.now() - this.threshold) { $.get('//a.4cdn.org/' + Main.board + '/threads.json', { onload: function() { var i, j, t, p, pages, threads, alive; if (this.status == 200) { alive = {}; pages = JSON.parse(this.responseText); for (i = 0; p = pages[i]; ++i) { threads = p.threads; for (j = 0; t = threads[j]; ++j) { if (ThreadHiding.hidden[t.no]) { alive[t.no] = 1; } } } ThreadHiding.hidden = alive; ThreadHiding.save(); localStorage.setItem(key, Date.now()); } else { console.log('Bad status code while purging threads'); } }, onerror: function() { console.log('Error while purging hidden threads'); } }); } }; ThreadHiding.save = function() { var i; for (i in this.hidden) { localStorage.setItem('4chan-hide-t-' + Main.board, JSON.stringify(this.hidden) ); return; } localStorage.removeItem('4chan-hide-t-' + Main.board); }; /** * Reply hiding */ var ReplyHiding = {}; ReplyHiding.init = function() { this.threshold = 7 * 86400000; this.hidden = {}; this.hiddenR = {}; this.hiddenRMap = {}; this.hasR = false; this.load(); }; ReplyHiding.isHidden = function(pid) { var sa = $.id('sa' + pid); return !sa || sa.hasAttribute('data-hidden'); }; ReplyHiding.toggle = function(pid) { this.load(); if (this.isHidden(pid)) { this.show(pid); } else { this.hide(pid); } this.save(); }; ReplyHiding.toggleR = function(pid) { var i, el, post, nodes, rid, parentPid; this.load(); if (parentPid = this.hiddenRMap['>>' + pid]) { this.showR(parentPid, parentPid); for (i in this.hiddenRMap) { if (this.hiddenRMap[i] == parentPid) { this.showR(i.slice(2)); } } } else { this.hideR(pid, pid); post = $.id('m' + pid); nodes = $.cls('postMessage'); for (i = 1; nodes[i] !== post; ++i) {} for (; el = nodes[i]; ++i) { if (ReplyHiding.shouldToggleR(el)) { rid = el.id.slice(1); this.hideR(rid, pid); } } } this.hasR = false; for (i in this.hiddenRMap) { this.hasR = true; break; } this.save(); }; ReplyHiding.shouldToggleR = function(el) { var j, ql, hit, quotes; if (el.parentNode.hasAttribute('data-pfx')) { return false; } quotes = $.qsa('#' + el.id + ' > .quotelink', el); if (!quotes[0]) { return false; } hit = this.hiddenRMap[quotes[0].textContent]; if (quotes.length === 1 && hit) { return hit; } else { for (j = 0; ql = quotes[j]; ++j) { if (!this.hiddenRMap[ql.textContent]) { return false; } } } return hit; }; ReplyHiding.show = function(pid) { $.removeClass($.id('pc' + pid), 'post-hidden'); $.id('sa' + pid).removeAttribute('data-hidden'); delete ReplyHiding.hidden[pid]; }; ReplyHiding.showR = function(pid, parentPid) { $.removeClass($.id('pc' + pid), 'post-hidden'); $.id('sa' + pid).removeAttribute('data-hidden'); delete ReplyHiding.hiddenRMap['>>' + pid]; if (parentPid) { delete ReplyHiding.hiddenR[parentPid]; } }; ReplyHiding.hide = function(pid) { $.addClass($.id('pc' + pid), 'post-hidden'); $.id('sa' + pid).setAttribute('data-hidden', pid); ReplyHiding.hidden[pid] = Date.now(); }; ReplyHiding.hideR = function(pid, parentPid) { $.addClass($.id('pc' + pid), 'post-hidden'); $.id('sa' + pid).setAttribute('data-hidden', pid); ReplyHiding.hiddenRMap['>>' + pid] = parentPid; if (pid === parentPid) { ReplyHiding.hiddenR[pid] = Date.now(); } ReplyHiding.hasR = true; }; ReplyHiding.load = function() { var storage; this.hasHiddenR = false; if (storage = localStorage.getItem('4chan-hide-r-' + Main.board)) { this.hidden = JSON.parse(storage); } if (storage = localStorage.getItem('4chan-hide-rr-' + Main.board)) { this.hiddenR = JSON.parse(storage); } }; ReplyHiding.purge = function() { var tid, now; now = Date.now(); for (tid in this.hidden) { if (now - this.hidden[tid] > this.threshold) { delete this.hidden[tid]; } } for (tid in this.hiddenR) { if (now - this.hiddenR[tid] > this.threshold) { delete this.hiddenR[tid]; } } this.save(); }; ReplyHiding.save = function() { var i, clr; clr = true; for (i in this.hidden) { localStorage.setItem('4chan-hide-r-' + Main.board, JSON.stringify(this.hidden) ); clr = false; break; } clr && localStorage.removeItem('4chan-hide-r-' + Main.board); clr = true; for (i in this.hiddenR) { localStorage.setItem('4chan-hide-rr-' + Main.board, JSON.stringify(this.hiddenR) ); clr = false; break; } clr && localStorage.removeItem('4chan-hide-rr-' + Main.board); }; /** * Thread watcher */ var ThreadWatcher = {}; ThreadWatcher.init = function() { var cnt, jumpTo, rect, el, awt; this.listNode = null; this.charLimit = 45; this.watched = {}; this.blacklisted = {}; this.isRefreshing = false; if (Main.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); } if (location.hash && (jumpTo = location.hash.split('lr')[1])) { if (jumpTo = $.id('pc' + jumpTo)) { if (jumpTo.nextElementSibling) { jumpTo = jumpTo.nextElementSibling; if (el = $.id('p' + jumpTo.id.slice(2))) { $.addClass(el, 'highlight'); } } rect = jumpTo.getBoundingClientRect(); if (rect.top < 0 || rect.bottom > document.documentElement.clientHeight) { window.scrollBy(0, rect.top); } } if (window.history && history.replaceState) { history.replaceState(null, '', location.href.split('#', 1)[0]); } } cnt = document.createElement('div'); cnt.id = 'threadWatcher'; cnt.className = 'extPanel reply'; cnt.setAttribute('data-trackpos', 'TW-position'); if (Main.hasMobileLayout) { cnt.style.display = 'none'; } else { if (Config['TW-position']) { cnt.style.cssText = Config['TW-position']; } else { cnt.style.left = '10px'; cnt.style.top = '380px'; } if (Config.fixedThreadWatcher) { cnt.style.position = 'fixed'; } else { cnt.style.position = ''; } } cnt.innerHTML = '
' + (Main.hasMobileLayout ? ('X') : '') + 'Thread Watcher' + (UA.hasCORS ? ('R
') : ''); this.listNode = document.createElement('ul'); this.listNode.id = 'watchList'; this.load(); if (Config.threadAutoWatcher) { if (awt = Main.getCookie('4chan_awt')) { Main.removeCookie('4chan_awt', '.' + $L.d(Main.board), '/' + Main.board + '/'); this.add(+awt, Main.board); this.save(); } if (document.forms.post) { el = $.el('input'); el.type = 'hidden'; el.name = 'awt'; el.value = '1'; document.forms.post.appendChild(el); } } if (Main.tid) { this.refreshCurrent(); } 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 (Main.hasMobileLayout) { if (Main.tid) { ThreadWatcher.initMobileButtons(); } } else if (!Main.tid && this.canAutoRefresh()) { this.refresh(); } }; ThreadWatcher.toggleList = function(e) { var el = $.id('threadWatcher'); e && e.preventDefault(); if (!Main.tid && 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' && !key[2] && e.newValue != e.oldValue) { ThreadWatcher.load(); ThreadWatcher.build(true); } }; 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(rebuildButtons) { 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] + '
  • '; } if (rebuildButtons) { ThreadWatcher.rebuildButtons(); } ThreadWatcher.listNode.innerHTML = html; }; ThreadWatcher.rebuildButtons = function() { var i, buttons, key, btn; buttons = $.cls('wbtn'); for (i = 0; btn = buttons[i]; ++i) { key = btn.getAttribute('data-id') + '-' + Main.board; if (ThreadWatcher.watched[key]) { if (!btn.hasAttribute('data-active')) { btn.src = Main.icons.watched; btn.setAttribute('data-active', '1'); } } else { if (btn.hasAttribute('data-active')) { btn.src = Main.icons.notwatched; btn.removeAttribute('data-active'); } } } }; ThreadWatcher.initMobileButtons = function() { var el, cnt, key, ref; el = document.createElement('img'); key = Main.tid + '-' + Main.board; if (ThreadWatcher.watched[key]) { el.src = Main.icons.watched; el.setAttribute('data-active', '1'); } else { el.src = Main.icons.notwatched; } el.className = 'extButton wbtn wbtn-' + key; el.setAttribute('data-cmd', 'watch'); el.setAttribute('data-id', Main.tid); el.alt = 'W'; cnt = document.createElement('span'); cnt.className = 'mobileib button'; cnt.appendChild(el); if (ref = $.cls('navLinks')[0]) { ref.appendChild(document.createTextNode(' ')); ref.appendChild(cnt); } if (ref = $.cls('navLinks')[3]) { ref.appendChild(document.createTextNode(' ')); ref.appendChild(cnt.cloneNode(true)); } }; 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) { var key; key = tid + '-' + (board || Main.board); if (this.watched[key]) { this.blacklisted[key] = 1; delete this.watched[key]; } else { this.add(tid, board, key); } this.save(); this.load(); this.build(true); }; ThreadWatcher.add = function(tid, board) { var key, label, sub, com, lastReply, thread; key = tid + '-' + (board || Main.board); sub = $.cls('subject', $.id('pi' + tid))[0].textContent; com = $.id('m' + tid).innerHTML; label = this.generateLabel(sub, com, tid); if ((thread = $.id('t' + tid)).children[1]) { lastReply = thread.lastElementChild.id.slice(2); } else { lastReply = tid; } this.watched[key] = [ label, lastReply, 0 ]; }; ThreadWatcher.addRaw = function(post, board) { var key, label; key = post.no + '-' + (board || Main.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 true; }; 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 (!Config.filter) { 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.src = Main.icons.rotate; 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.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] = Parser.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').src = Main.icons.refresh; 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.src = Main.icons.rotate; ThreadWatcher.isRefreshing = true; ThreadWatcher.setRefreshTimestamp(); for (key in ThreadWatcher.watched) { setTimeout(ThreadWatcher.fetch, to, key, ++i == total ? img : null); to += 200; } } }; ThreadWatcher.refreshCurrent = function(rebuild) { var key, thread, lastReply; key = Main.tid + '-' + Main.board; if (this.watched[key]) { if ((thread = $.id('t' + Main.tid)).children[1]) { lastReply = thread.lastElementChild.id.slice(2); } else { lastReply = Main.tid; } if (this.watched[key][1] < lastReply) { this.watched[key][1] = lastReply; } this.watched[key][2] = 0; this.watched[key][4] = 0; this.save(); if (rebuild) { this.build(); } } }; ThreadWatcher.setLastRead = function(pid, tid) { var key = tid + '-' + Main.board; if (this.watched[key]) { this.watched[key][1] = pid; this.watched[key][2] = 0; this.watched[key][4] = 0; this.save(); this.build(); } }; ThreadWatcher.onRefreshEnd = function(img) { img.src = Main.icons.refresh; this.isRefreshing = false; this.save(); this.load(); this.build(); }; 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 = Parser.parseThreadJSON(this.responseText); lastReply = ThreadWatcher.watched[key][1]; newReplies = 0; if (!ThreadWatcher.watched[key][4]) { trackedReplies = Parser.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); }; /** * Thread expansion */ var ThreadExpansion = {}; ThreadExpansion.init = function() { this.enabled = UA.hasCORS; this.fetchXhr = null; }; ThreadExpansion.expandComment = function(link) { var ids, tid, pid, abbr; if (!(ids = link.getAttribute('href').match(/^(?:thread\/)([0-9]+)#p([0-9]+)$/))) { return; } tid = ids[1]; pid = ids[2]; abbr = link.parentNode; abbr.textContent = 'Loading...'; $.get('//a.4cdn.org/' + Main.board + '/thread/' + tid + '.json', { onload: function() { var i, msg, post, posts; if (this.status == 200) { msg = $.id('m' + pid); posts = Parser.parseThreadJSON(this.responseText); if (tid == pid) { post = posts[0]; } else { for (i = posts.length - 1; i > 0; i--) { if (posts[i].no == pid) { post = posts[i]; break; } } } if (post) { post = Parser.buildHTMLFromJSON(post, Main.board); msg.innerHTML = $.cls('postMessage', post)[0].innerHTML; if (Parser.prettify) { Parser.parseMarkup(msg); } if (window.math_tags) { Parser.parseMathOne(msg); } } else { abbr.textContent = "This post doesn't exist anymore."; } } else if (this.status == 404) { abbr.textContent = "This thread doesn't exist anymore."; } else { abbr.textContent = 'Connection Error'; console.log('ThreadExpansion: ' + this.status + ' ' + this.statusText); } }, onerror: function() { abbr.textContent = 'Connection Error'; console.log('ThreadExpansion: xhr failed'); } } ); }; ThreadExpansion.toggle = function(tid) { var thread, msg, expmsg, summary, tmp; thread = $.id('t' + tid); summary = thread.children[1]; if (thread.hasAttribute('data-truncated')) { msg = $.id('m' + tid); expmsg = msg.nextSibling; } if ($.hasClass(thread, 'tExpanded')) { thread.className = thread.className.replace(' tExpanded', ' tCollapsed'); summary.children[0].src = Main.icons.plus; summary.children[1].style.display = 'inline'; summary.children[2].style.display = 'none'; if (msg) { tmp = msg.innerHTML; msg.innerHTML = expmsg.textContent; expmsg.textContent = tmp; } } else if ($.hasClass(thread, 'tCollapsed')) { thread.className = thread.className.replace(' tCollapsed', ' tExpanded'); summary.children[0].src = Main.icons.minus; summary.children[1].style.display = 'none'; summary.children[2].style.display = 'inline'; if (msg) { tmp = msg.innerHTML; msg.innerHTML = expmsg.textContent; expmsg.textContent = tmp; } } else { summary.children[0].src = Main.icons.rotate; if (!ThreadExpansion.fetchXhr) { ThreadExpansion.fetch(tid); } } }; ThreadExpansion.fetch = function(tid) { ThreadExpansion.fetchXhr = $.get( '//a.4cdn.org/' + Main.board + '/thread/' + tid + '.json', { onload: function() { var i, p, n, frag, thread, tail, posts, msg, metacap, expmsg, summary, abbr; ThreadExpansion.fetchXhr = null; thread = $.id('t' + tid); summary = thread.children[1]; if (this.status == 200) { tail = $.cls('reply', thread); posts = Parser.parseThreadJSON(this.responseText); if (!Config.revealSpoilers && posts[0].custom_spoiler) { Parser.setCustomSpoiler(Main.board, posts[0].custom_spoiler); } frag = document.createDocumentFragment(); if (tail[0]) { tail = +tail[0].id.slice(1); for (i = 1; p = posts[i]; ++i) { if (p.no < tail) { n = Parser.buildHTMLFromJSON(p, Main.board); n.className += ' rExpanded'; frag.appendChild(n); } else { break; } } } else { for (i = 1; p = posts[i]; ++i) { n = Parser.buildHTMLFromJSON(p, Main.board); n.className += ' rExpanded'; frag.appendChild(n); } } msg = $.id('m' + tid); if ((abbr = $.cls('abbr', msg)[0]) && /^Comment/.test(abbr.textContent)) { thread.setAttribute('data-truncated', '1'); expmsg = document.createElement('div'); expmsg.style.display = 'none'; expmsg.textContent = msg.innerHTML; msg.parentNode.insertBefore(expmsg, msg.nextSibling); if (metacap = $.cls('capcodeReplies', msg)[0]) { msg.innerHTML = posts[0].com + '

    '; msg.appendChild(metacap); } else { msg.innerHTML = posts[0].com; } if (Parser.prettify) { Parser.parseMarkup(msg); } if (window.math_tags) { Parser.parseMathOne(msg); } } thread.insertBefore(frag, summary.nextSibling); Parser.parseThread(tid, 1, i - 1); thread.className += ' tExpanded'; summary.children[0].src = Main.icons.minus; summary.children[1].style.display = 'none'; summary.children[2].style.display = 'inline'; } else if (this.status == 404) { summary.children[0].src = Main.icons.plus; summary.children[0].display = 'none'; summary.children[1].textContent = "This thread doesn't exist anymore."; } else { summary.children[0].src = Main.icons.plus; console.log('ThreadExpansion: ' + this.status + ' ' + this.statusText); } }, onerror: function() { ThreadExpansion.fetchXhr = null; $.id('t' + tid).children[1].children[0].src = Main.icons.plus; console.log('ThreadExpansion: xhr failed'); } } ); }; /** * Thread updater */ var ThreadUpdater = {}; ThreadUpdater.init = function() { if (!UA.hasCORS || window.thread_archived) { return; } this.enabled = true; this.pageTitle = document.title; this.unreadCount = 0; this.auto = this.hadAuto = false; this.delayId = 0; this.delayIdHidden = 4; this.delayRange = [ 10, 15, 20, 30, 60, 90, 120, 180, 240, 300 ]; this.timeLeft = 0; this.interval = null; this.tailSize = window.tailSize || 0; this.lastUpdated = Date.now(); this.lastModified = '0'; this.lastModifiedTail = '0'; this.lastReply = null; this.currentIcon = null; this.iconPath = '//s.4cdn.org/image/'; this.iconNode = $.qs('link[rel="shortcut icon"]', document.head); this.iconNode.type = 'image/x-icon'; this.defaultIcon = this.iconNode.getAttribute('href').replace(this.iconPath, ''); this.deletionQueue = {}; if (Config.updaterSound) { this.audioEnabled = false; this.audio = document.createElement('audio'); this.audio.src = '//s.4cdn.org/media/beep.ogg'; } this.hidden = 'hidden'; this.visibilitychange = 'visibilitychange'; this.adsTTL = 900000; this.adsReloadTs = 0; if (typeof document.hidden === 'undefined') { if ('mozHidden' in document) { this.hidden = 'mozHidden'; this.visibilitychange = 'mozvisibilitychange'; } else if ('webkitHidden' in document) { this.hidden = 'webkitHidden'; this.visibilitychange = 'webkitvisibilitychange'; } else if ('msHidden' in document) { this.hidden = 'msHidden'; this.visibilitychange = 'msvisibilitychange'; } } this.initControls(); document.addEventListener('scroll', this.onScroll, false); if (Config.alwaysAutoUpdate || sessionStorage.getItem('4chan-auto-' + Main.tid)) { this.start(); } }; ThreadUpdater.apiUrlFilter = null; ThreadUpdater.buildMobileControl = function(el, bottom) { var wrap, cnt, ctrl, cb, label, oldBtn, btn; bottom = (bottom ? 'Bot' : ''); wrap = document.createElement('div'); wrap.className = 'btn-row'; // Update button oldBtn = el.parentNode; btn = oldBtn.cloneNode(true); btn.innerHTML = ''; wrap.appendChild(btn); cnt = el.parentNode.parentNode; ctrl = document.createElement('span'); ctrl.className = 'mobileib button'; // Auto checkbox label = document.createElement('label'); cb = document.createElement('input'); cb.type = 'checkbox'; cb.setAttribute('data-cmd', 'auto'); this['autoNode' + bottom] = cb; label.appendChild(cb); label.appendChild(document.createTextNode('Auto')); ctrl.appendChild(label); wrap.appendChild(document.createTextNode(' ')); wrap.appendChild(ctrl); // Status label label = document.createElement('div'); label.className = 'mobile-tu-status'; wrap.appendChild(this['statusNode' + bottom] = label); cnt.appendChild(wrap); // Remove Update button oldBtn.parentNode.removeChild(oldBtn); if (cnt = $.id('mpostform')) { cnt.parentNode.style.marginTop = ''; } }; ThreadUpdater.buildDesktopControl = function(bottom) { var frag, el, label, navlinks; bottom = (bottom ? 'Bot' : ''); frag = document.createDocumentFragment(); // Update button frag.appendChild(document.createTextNode(' [')); el = document.createElement('a'); el.href = ''; el.textContent = 'Update'; el.setAttribute('data-cmd', 'update'); frag.appendChild(el); frag.appendChild(document.createTextNode(']')); // Auto checkbox frag.appendChild(document.createTextNode(' [')); label = document.createElement('label'); el = document.createElement('input'); el.type = 'checkbox'; el.title = 'Fetch new replies automatically'; el.setAttribute('data-cmd', 'auto'); this['autoNode' + bottom] = el; label.appendChild(el); label.appendChild(document.createTextNode('Auto')); frag.appendChild(label); frag.appendChild(document.createTextNode('] ')); if (Config.updaterSound) { // Sound checkbox frag.appendChild(document.createTextNode(' [')); label = document.createElement('label'); el = document.createElement('input'); el.type = 'checkbox'; el.title = 'Play a sound on new replies to your posts'; el.setAttribute('data-cmd', 'sound'); this['soundNode' + bottom] = el; label.appendChild(el); label.appendChild(document.createTextNode('Sound')); frag.appendChild(label); frag.appendChild(document.createTextNode('] ')); } // Status label frag.appendChild( this['statusNode' + bottom] = document.createElement('span') ); if (bottom) { navlinks = $.cls('navLinks' + bottom)[0]; } else { navlinks = $.cls('navLinks')[1]; } if (navlinks) { navlinks.appendChild(frag); } }; ThreadUpdater.initControls = function() { // Mobile if (Main.hasMobileLayout) { this.buildMobileControl($.id('refresh_top')); this.buildMobileControl($.id('refresh_bottom'), true); } // Desktop else { this.buildDesktopControl(); this.buildDesktopControl(true); } }; ThreadUpdater.start = function() { if (this.dead) { return; } this.auto = this.hadAuto = true; this.autoNode.checked = this.autoNodeBot.checked = true; this.force = this.updating = false; if (this.hidden) { document.addEventListener(this.visibilitychange, this.onVisibilityChange, false); } this.delayId = 0; this.timeLeft = this.delayRange[0]; this.pulse(); sessionStorage.setItem('4chan-auto-' + Main.tid, 1); }; ThreadUpdater.stop = function(manual) { clearTimeout(this.interval); this.auto = this.updating = this.force = false; this.autoNode.checked = this.autoNodeBot.checked = false; if (this.hidden) { document.removeEventListener(this.visibilitychange, this.onVisibilityChange, false); } if (manual) { this.setStatus(''); this.setIcon(null); } sessionStorage.removeItem('4chan-auto-' + Main.tid); }; ThreadUpdater.pulse = function() { var self = ThreadUpdater; if (self.timeLeft === 0) { self.update(); } else { self.setStatus(self.timeLeft--); self.interval = setTimeout(self.pulse, 1000); } }; ThreadUpdater.adjustDelay = function(postCount) { if (postCount === 0) { if (!this.force) { if (this.delayId < this.delayRange.length - 1) { ++this.delayId; } } } else { this.delayId = document[this.hidden] ? this.delayIdHidden : 0; } this.timeLeft = this.delayRange[this.delayId]; if (this.auto) { this.pulse(); } }; ThreadUpdater.onVisibilityChange = function() { var self = ThreadUpdater; if (document[self.hidden] && self.delayId < self.delayIdHidden) { self.delayId = self.delayIdHidden; } else { self.delayId = 0; } self.timeLeft = self.delayRange[0]; clearTimeout(self.interval); self.pulse(); }; ThreadUpdater.onScroll = function() { if (ThreadUpdater.hadAuto && (document.documentElement.scrollHeight <= (Math.ceil(window.innerHeight + window.pageYOffset)) && !document[ThreadUpdater.hidden])) { ThreadUpdater.clearUnread(); } }; ThreadUpdater.clearUnread = function() { if (!this.dead) { this.setIcon(null); } if (this.lastReply) { this.unreadCount = 0; document.title = this.pageTitle; $.removeClass(this.lastReply, 'newPostsMarker'); this.lastReply = null; } }; ThreadUpdater.forceUpdate = function() { ThreadUpdater.force = true; ThreadUpdater.update(); }; ThreadUpdater.toggleAuto = function() { if (this.updating) { return; } this.auto ? this.stop(true) : this.start(); }; ThreadUpdater.toggleSound = function() { this.soundNode.checked = this.soundNodeBot.checked = this.audioEnabled = !this.audioEnabled; }; ThreadUpdater.update = function(full) { var self, isTail, url; self = ThreadUpdater; if (self.updating || self.dead) { return; } clearTimeout(self.interval); self.updating = true; self.setStatus('Updating...'); isTail = !full && self.checkTailUpdate(); url = '//a.4cdn.org/' + Main.board + '/thread/' + Main.tid + (isTail ? '-tail' : '') + '.json'; if (self.apiUrlFilter) { url = self.apiUrlFilter(url); } $.get(url, { onload: self.onload, onerror: self.onerror, istail: isTail }, { 'If-Modified-Since': isTail ? self.lastModifiedTail : self.lastModified } ); }; ThreadUpdater.checkTailUpdate = function() { var self, nodes, el, dtr, dtp; self = ThreadUpdater; if (!self.tailSize) { return false; } nodes = $.cls('replyContainer'); el = nodes[nodes.length - self.tailSize]; if (!el) { return true; } el = $.cls('dateTime', el)[0]; dtp = (0 | (self.lastUpdated / 1000)) - (+el.getAttribute('data-utc')); dtr = 0 | ((Date.now() - self.lastUpdated) / 1000); return dtp > dtr; }; ThreadUpdater.checkTailValid = function(posts) { var op = posts[0]; if (!op || !op.tail_id) { ThreadUpdater.tailSize = 0; return false; } return !!$.id('p' + op.tail_id); }; ThreadUpdater.reloadAds = function() { if (!window.Danbo || !window.reloadAdsDanbo) { return; } let self = ThreadUpdater; let now = Date.now(); if (!self.adsReloadTs) { self.adsReloadTs = now; return; } if (now - self.adsReloadTs < self.adsTTL) { return; } self.adsReloadTs = now; window.reloadAdsDanbo(); }; ThreadUpdater.onload = function() { var i, state, self, nodes, thread, newposts, frag, lastrep, lastid, op, doc, autoscroll, count, fromQR, lastRepPos; self = ThreadUpdater; nodes = []; self.setStatus(''); if (this.status == 200) { newposts = Parser.parseThreadJSON(this.responseText); if (this.istail) { if (!self.checkTailValid(newposts)) { self.updating = false; self.update(true); return; } self.lastModifiedTail = this.getResponseHeader('Last-Modified'); } else { if (newposts[0].tail_size !== self.tailSize) { self.tailSize = newposts[0].tail_size || 0; } self.lastModified = this.getResponseHeader('Last-Modified'); } thread = $.id('t' + Main.tid); lastrep = thread.children[thread.childElementCount - 1]; lastid = +lastrep.id.slice(2); state = !!newposts[0].archived; if (window.thread_archived !== undefined && state != window.thread_archived) { QR.enabled && $.id('quickReply') && QR.lock(); Main.setThreadState('archived', state); } state = !!newposts[0].closed; if (state != Main.threadClosed) { if (newposts[0].archived) { state = false; } else if (QR.enabled && $.id('quickReply')) { if (state) { QR.lock(); } else { QR.unlock(); } } Main.setThreadState('closed', state); } state = !!newposts[0].sticky; if (state != Main.threadSticky) { Main.setThreadState('sticky', state); } state = !!newposts[0].imagelimit; if (QR.enabled && state != QR.fileDisabled) { QR.fileDisabled = state; } if (!Config.revealSpoilers && newposts[0].custom_spoiler) { Parser.setCustomSpoiler(Main.board, newposts[0].custom_spoiler); } for (i = newposts.length - 1; i >= 0; i--) { if (newposts[i].no <= lastid) { break; } nodes.push(newposts[i]); } count = nodes.length; if (count == 1 && QR.lastReplyId == nodes[0].no) { fromQR = true; QR.lastReplyId = null; } if (count) { doc = document.documentElement; autoscroll = ( Config.autoScroll && document[self.hidden] && doc.scrollHeight == Math.ceil(window.innerHeight + window.pageYOffset) ); if (window.chrome && document.activeElement) { if (document.activeElement.href || document.activeElement.type === 'checkbox') { document.activeElement.blur(); } } frag = document.createDocumentFragment(); for (i = nodes.length - 1; i >= 0; i--) { frag.appendChild(Parser.buildHTMLFromJSON(nodes[i], Main.board)); } thread.appendChild(frag); lastRepPos = lastrep.offsetTop; Parser.hasYouMarkers = false; Parser.hasHighlightedPosts = false; Parser.parseThread(thread.id.slice(1), -nodes.length); if (lastRepPos != lastrep.offsetTop) { window.scrollBy(0, lastrep.offsetTop - lastRepPos); } if (!fromQR) { if (!self.force && doc.scrollHeight > window.innerHeight) { if (!self.lastReply && lastid != Main.tid) { (self.lastReply = lastrep.lastChild).className += ' newPostsMarker'; } if (Parser.hasYouMarkers) { self.setIcon('rep'); if (self.audioEnabled && document[self.hidden]) { self.audio.play(); } } else if (Parser.hasHighlightedPosts && self.currentIcon !== 'rep') { self.setIcon('hl'); } else if (self.unreadCount === 0) { self.setIcon('new'); } self.unreadCount += count; document.title = '(' + self.unreadCount + ') ' + self.pageTitle; } else { self.setStatus(count + ' new post' + (count > 1 ? 's' : '')); } } if (autoscroll) { window.scrollTo(0, document.documentElement.scrollHeight); } if (Config.threadWatcher) { ThreadWatcher.refreshCurrent(true); } if (Config.threadStats) { op = newposts[0]; ThreadStats.update(op.replies, op.images, op.unique_ips, op.bumplimit, op.imagelimit); } if (self.force) { self.reloadAds(); } UA.dispatchEvent('4chanThreadUpdated', { count: count }); } else { self.setStatus('No new posts'); } if (newposts[0].archived) { self.setError('This thread is archived'); if (!self.dead) { self.setIcon('dead'); window.thread_archived = true; self.dead = true; self.stop(); } } } else if (this.status === 304 || this.status === 0) { self.setStatus('No new posts'); } else if (this.status === 404) { if (this.istail) { self.updating = false; self.update(true); return; } self.setIcon('dead'); self.setError('This thread has been pruned or deleted'); self.dead = true; self.stop(); return; } self.lastUpdated = Date.now(); self.adjustDelay(nodes.length); self.updating = self.force = false; }; ThreadUpdater.onerror = function() { var self = ThreadUpdater; if (UA.isOpera && !this.statusText && this.status === 0) { self.setStatus('No new posts'); } else { self.setError('Connection Error'); } self.lastUpdated = Date.now(); self.adjustDelay(0); self.updating = self.force = false; }; ThreadUpdater.setStatus = function(msg) { this.statusNode.textContent = this.statusNodeBot.textContent = msg; }; ThreadUpdater.setError = function(msg) { this.statusNode.innerHTML = this.statusNodeBot.innerHTML = '' + msg + ''; }; ThreadUpdater.setIcon = function(type) { var icon; if (type === null) { icon = this.defaultIcon; } else { icon = this.icons[Main.type + type]; } this.currentIcon = type; this.iconNode.href = this.iconPath + icon; document.head.appendChild(this.iconNode); }; ThreadUpdater.icons = { wsnew: 'favicon-ws-newposts.ico', nwsnew: 'favicon-nws-newposts.ico', wsrep: 'favicon-ws-newreplies.ico', nwsrep: 'favicon-nws-newreplies.ico', wsdead: 'favicon-ws-deadthread.ico', nwsdead: 'favicon-nws-deadthread.ico', wshl: 'favicon-ws-newfilters.ico', nwshl: 'favicon-nws-newfilters.ico' }; /** * Thread stats */ var ThreadStats = {}; ThreadStats.init = function() { var cnt; this.nodeTop = document.createElement('div'); this.nodeTop.className = 'thread-stats'; if (!Main.hasMobileLayout) { this.nodeBot = this.nodeTop.cloneNode(false); cnt = $.cls('navLinks'); cnt[1] && cnt[1].appendChild(this.nodeTop); cnt[2] && cnt[2].appendChild(this.nodeBot); } else { this.nodeBot = {}; cnt = $.cls('navLinks'); if (cnt[0]) { cnt = cnt[cnt.length - 1].nextElementSibling; cnt.parentNode.insertBefore(this.nodeTop, cnt); } } this.pageNumber = null; this.update(null, null, null, window.bumplimit, window.imagelimit); if (!window.thread_archived) { this.updatePageNumber(); this.pageInterval = setInterval(this.updatePageNumber, 3 * 60000); } }; ThreadStats.update = function(replies, images, ips, isBumpFull, isImageFull) { var stats; if (replies === null) { replies = $.cls('replyContainer').length; images = $.cls('fileText').length - ($.id('fT' + Main.tid) ? 1 : 0); } stats = []; if (Main.threadSticky) { stats.push('Sticky'); } if (window.thread_archived) { stats.push('Archived'); } else if (Main.threadClosed) { stats.push('Closed'); } if (isBumpFull) { stats.push('' + replies + ''); } else { stats.push('' + replies + ''); } if (isImageFull) { stats.push('' + images + ''); } else { stats.push('' + images + ''); } if (!window.thread_archived) { if (window.unique_ips) { stats.push('' + (ips || window.unique_ips) + ''); } stats.push('' + (this.pageNumber || '?') + ''); } this.nodeTop.innerHTML = this.nodeBot.innerHTML = stats.join(' / '); }; ThreadStats.updatePageNumber = function() { $.get('//a.4cdn.org/' + Main.board + '/threads.json', { onload: ThreadStats.onCatalogLoad, onerror: ThreadStats.onCatalogError } ); }; ThreadStats.onCatalogLoad = function() { var self, i, j, k, n, page, post, threads, catalog, tid, nodes; self = ThreadStats; if (this.status == 200) { tid = +Main.tid; catalog = JSON.parse(this.responseText); for (i = 0; page = catalog[i]; ++i) { threads = page.threads; for (j = 0; post = threads[j]; ++j) { if (post.no == tid) { nodes = $.cls('ts-page'); for (k = 0; n = nodes[k]; ++k) { n.textContent = page.page; } self.pageNumber = page.page; return; } } } clearInterval(self.pageInterval); } else { ThreadStats.onCatalogError(); } }; ThreadStats.onCatalogError = function() { console.log('ThreadStats: couldn\'t get the catalog (' + this.status + ')'); }; /** * Filter */ var Filter = {}; Filter.init = function() { this.entities = document.createElement('div'); Filter.load(); }; Filter.onClick = function(e) { var cmd; if (cmd = e.target.getAttribute('data-cmd')) { switch (cmd) { case 'filters-add': Filter.add(); break; case 'filters-save': Filter.save(); Filter.close(); break; case 'filters-close': Filter.close(); break; case 'filters-palette': Filter.openPalette(e.target); break; case 'filters-palette-close': Filter.closePalette(); break; case 'filters-palette-clear': Filter.clearPalette(); break; case 'filters-up': Filter.moveUp(e.target.parentNode.parentNode); break; case 'filters-del': Filter.remove(e.target.parentNode.parentNode); break; case 'filters-help-open': Filter.openHelp(); break; case 'filters-help-close': Filter.closeHelp(); break; } } }; Filter.onPaletteClick = function(e) { var cmd; if (cmd = e.target.getAttribute('data-cmd')) { switch (cmd) { case 'palette-pick': Filter.pickColor(e.target); break; case 'palette-clear': Filter.pickColor(e.target, true); break; case 'palette-close': Filter.closePalette(); break; } } }; 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; }; Filter.exec = function(cnt, pi, msg, tid) { var i, el, trip, name, com, uid, sub, fname, f, filters, hit, currentBoard; if (Parser.trackedReplies && Parser.trackedReplies['>>' + pi.id.slice(2)]) { return false; } currentBoard = Main.board; filters = Filter.activeFilters; hit = false; for (i = 0; f = filters[i]; ++i) { // boards if (f.boards && !f.boards[currentBoard]) { continue; } // tripcode if (f.type === 0) { if ((trip !== undefined || (trip = pi.getElementsByClassName('postertrip')[0]) ) && f.pattern == trip.textContent) { hit = true; break; } } // name else if (f.type === 1) { if ((name || (name = pi.getElementsByClassName('name')[0])) && f.pattern == name.textContent) { hit = true; break; } } // comment else if (f.type === 2) { if (com === undefined) { this.entities.innerHTML = msg.innerHTML.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 ((uid || ((uid = pi.getElementsByClassName('posteruid')[0]) && (uid = uid.firstElementChild.textContent) ) ) && f.pattern == uid) { hit = true; break; } } // subject else if (!Main.tid && f.type === 5) { if ((sub || ((sub = pi.getElementsByClassName('subject')[0]) && (sub = sub.textContent) ) ) && f.pattern.test(sub)) { hit = true; break; } } // filename else if (f.type === 6) { if (fname === undefined) { if ((fname = pi.parentNode.getElementsByClassName('fileText')[0])) { fname = fname.firstElementChild.textContent; } else { fname = ''; } } if (f.pattern.test(fname)) { hit = true; break; } } } if (hit) { if (f.hide) { if (tid && Config.hideStubs && !$.cls('stickyIcon', cnt)[0]) { cnt.style.display = cnt.nextElementSibling.style.display = 'none'; } else { cnt.className += ' post-hidden'; el = document.createElement('span'); if (!tid) { el.textContent = '[View]'; el.setAttribute('data-filtered', '1'); el.setAttribute('data-cmd', 'unfilter'); } else { el.innerHTML = '[View]'; } el.className = 'filter-preview'; pi.appendChild(el); } return true; } else { cnt.className += ' filter-hl'; cnt.style.boxShadow = '-3px 0 ' + f.color; Parser.hasHighlightedPosts = true; } } return false; }; Filter.unfilter = function(t) { var cnt = t.parentNode.parentNode; QuotePreview.remove(); $.removeClass(cnt, 'post-hidden'); t.parentNode.removeChild(t); }; Filter.load = function() { var i, j, f, rawFilters, rawPattern, fid, regexEscape, regexType, wordSepS, wordSepE, words, inner, regexWildcard, replaceWildcard, tmp, boards, pattern, match; 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); } }; Filter.addSelection = function() { var text, type, node, sel = UA.getSelection(true); if (Filter.open() === false) { return; } if (typeof sel == 'string') { text = sel.trim(); } else { node = sel.anchorNode.parentNode; text = sel.toString().trim(); if ($.hasClass(node, 'name')) { type = 1; } else if ($.hasClass(node, 'postertrip')) { type = 0; } else if ($.hasClass(node, 'subject')) { type = 5; } else if ($.hasClass(node, 'posteruid') || $.hasClass(node, 'hand')) { type = 4; } else if ($.hasClass(node, 'fileText')) { type = 6; } else { type = 2; } } Filter.add(text, type); }; Filter.openHelp = function() { var cnt; if ($.id('filtersHelp')) { return; } cnt = document.createElement('div'); cnt.id = 'filtersHelp'; cnt.className = 'UIPanel'; cnt.setAttribute('data-cmd', 'filters-help-close'); cnt.innerHTML = '\
    Filters & Highlights Help\ Close
    \

    Tripcode, Name and ID filters:

    \ \

    Comment, Subject and E-mail filters:

    \ \ \ \ \ \

    Colors:

    \ \

    Boards:

    \ \

    Auto-watching:

    \ \

    Shortcut:

    \ '; document.body.appendChild(cnt); cnt.addEventListener('click', this.onClick, false); }; Filter.closeHelp = function() { var cnt; if (cnt = $.id('filtersHelp')) { cnt.removeEventListener('click', this.onClick, false); document.body.removeChild(cnt); } }; Filter.open = function() { var i, f, cnt, rawFilters, filterList; if ($.id('filtersMenu')) { return false; } cnt = document.createElement('div'); cnt.id = 'filtersMenu'; cnt.className = 'UIPanel'; cnt.style.display = 'none'; cnt.setAttribute('data-cmd', 'filters-close'); cnt.innerHTML = '\
    Filters & Highlights\ HelpClose
    \ \ \ \ \ \ \ \ \ \ \
    OnPatternBoardsTypeColorAutoHideDel
    \ \ \
    '; document.body.appendChild(cnt); cnt.addEventListener('click', this.onClick, false); filterList = $.id('filter-list'); if (rawFilters = localStorage.getItem('4chan-filters')) { rawFilters = JSON.parse(rawFilters); for (i = 0; f = rawFilters[i]; ++i) { filterList.appendChild(this.buildEntry(f, i)); } } cnt.style.display = ''; }; Filter.close = function() { var cnt; if (cnt = $.id('filtersMenu')) { this.closePalette(); cnt.removeEventListener('click', this.onClick, false); document.body.removeChild(cnt); } }; Filter.moveUp = function(el) { var prev; if (prev = el.previousElementSibling) { el.parentNode.insertBefore(el, prev); } }; Filter.add = function(pattern, type, boards) { var filter, id, el; filter = { active: true, type: type || 0, pattern: pattern || '', boards: boards || '', color: '', auto: false, hide: false }; id = this.getNextFilterId(); el = this.buildEntry(filter, id); $.id('filter-list').appendChild(el); $.cls('fPattern', el)[0].focus(); }; Filter.remove = function(tr) { $.id('filter-list').removeChild(tr); }; Filter.save = function() { var i, rawFilters, entries, tr, f, color, type; rawFilters = []; entries = $.id('filter-list').children; for (i = 0; tr = entries[i]; ++i) { type = tr.children[4].firstChild; f = { active: tr.children[1].firstChild.checked, pattern: tr.children[2].firstChild.value, boards: tr.children[3].firstChild.value, type: +type.options[type.selectedIndex].value, auto: tr.children[6].firstChild.checked, hide: tr.children[7].firstChild.checked }; color = tr.children[5].firstChild; if (!color.hasAttribute('data-nocolor')) { f.color = color.style.backgroundColor; } rawFilters.push(f); } if (rawFilters[0]) { localStorage.setItem('4chan-filters', JSON.stringify(rawFilters)); } else { localStorage.removeItem('4chan-filters'); } }; Filter.getNextFilterId = function() { var i, j, max, entries = $.id('filter-list').children; if (!entries.length) { return 0; } else { max = 0; for (i = 0; j = entries[i]; ++i) { j = +j.id.slice(7); if (j > max) { max = j; } } return max + 1; } }; Filter.buildEntry = function(filter, id) { var tr, html, sel; tr = document.createElement('tr'); tr.id = 'filter-' + id; html = ''; // Move up html += ''; // On html += '' : '>'); // Pattern html += ''; // Boards html += ''; // FIXME if (filter.type === 3) { filter.type = 4; } // Type sel = [ '', '', '', '', '', '', '' ]; sel[filter.type] = ' selected="selected"'; html += ''; // Color html += ''; } html += ''; // Auto html += '' : '>'); // Hide html += '' : '>'); // Del html += '×'; tr.innerHTML = html; return tr; }; Filter.buildPalette = function(id) { var i, j, cnt, html, colors, rowCount, colCount; colors = [ ['#E0B0FF', '#F2F3F4', '#7DF9FF', '#FFFF00'], ['#FBCEB1', '#FFBF00', '#ADFF2F', '#0047AB'], ['#00A550', '#007FFF', '#AF0A0F', '#B5BD68'] ]; rowCount = colors.length; colCount = colors[0].length; html = '
    '; for (i = 0; i < rowCount; ++i) { html += ''; for (j = 0; j < colCount; ++j) { html += ''; } html += ''; } html += '
    Custom\
    \
    \ [Close]\ [Clear]
    '; cnt = document.createElement('div'); cnt.id = 'filter-palette'; cnt.setAttribute('data-target', id); cnt.setAttribute('data-cmd', 'palette-close'); cnt.className = 'UIMenu'; cnt.innerHTML = html; return cnt; }; Filter.openPalette = function(target) { var el, pos, id, picker; Filter.closePalette(); pos = target.getBoundingClientRect(); id = target.parentNode.parentNode.id.slice(7); el = Filter.buildPalette(id); document.body.appendChild(el); $.id('filter-palette').addEventListener('click', Filter.onPaletteClick, false); $.id('palette-custom-input').addEventListener('keyup', Filter.setCustomColor, false); picker = el.firstElementChild; picker.style.cssText = 'top:' + pos.top + 'px;left:' + (pos.left - picker.clientWidth - 10) + 'px;'; }; Filter.closePalette = function() { var el; if (el = $.id('filter-palette')) { $.id('filter-palette').removeEventListener('click', Filter.onPaletteClick, false); $.id('palette-custom-input').removeEventListener('keyup', Filter.setCustomColor, false); el.parentNode.removeChild(el); } }; Filter.pickColor = function(el, clear) { var id, target; id = $.id('filter-palette').getAttribute('data-target'); target = $.id('filter-' + id); if (!target) { return; } target = $.cls('colorbox', target)[0]; if (clear === true) { target.setAttribute('data-nocolor', '1'); target.innerHTML = '∕'; target.style.background = ''; } else { target.removeAttribute('data-nocolor'); target.innerHTML = ''; target.style.background = el.style.backgroundColor; } Filter.closePalette(); }; Filter.setCustomColor = function() { var input, box; input = $.id('palette-custom-input'); box = $.id('palette-custom-ok'); box.style.backgroundColor = input.value; }; /** * ID colors */ var IDColor = { css: 'padding: 0 5px; border-radius: 6px; font-size: 0.8em;', ids: {} }; IDColor.init = function() { var style; if (window.user_ids) { this.enabled = true; style = document.createElement('style'); style.setAttribute('type', 'text/css'); style.textContent = '.posteruid .hand {' + this.css + '}'; document.head.appendChild(style); } }; IDColor.compute = function(str) { var rgb, hash; rgb = []; hash = $.hash(str); rgb[0] = (hash >> 24) & 0xFF; rgb[1] = (hash >> 16) & 0xFF; rgb[2] = (hash >> 8) & 0xFF; rgb[3] = ((rgb[0] * 0.299) + (rgb[1] * 0.587) + (rgb[2] * 0.114)) > 125; this.ids[str] = rgb; return rgb; }; IDColor.apply = function(uid) { var rgb; rgb = IDColor.ids[uid.textContent] || IDColor.compute(uid.textContent); uid.style.cssText = '\ background-color: rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ');\ color: ' + (rgb[3] ? 'black;' : 'white;'); }; IDColor.applyRemote = function(uid) { this.apply(uid); uid.style.cssText += this.css; }; /** * SWF embed */ var SWFEmbed = {}; SWFEmbed.init = function() { if (Main.tid) { this.processThread(); } else if (!Main.hasMobileLayout) { this.processIndex(); } }; SWFEmbed.processThread = function() { var fileText, el; fileText = $.id('fT' + Main.tid); if (!fileText) { return; } el = document.createElement('a'); el.href = 'javascript:;'; el.textContent = 'Embed'; el.addEventListener('click', SWFEmbed.toggleThread, false); fileText.appendChild(document.createTextNode('-[')); fileText.appendChild(el); fileText.appendChild(document.createTextNode(']')); }; SWFEmbed.processIndex = function() { var i, tr, el, cnt, nodes, srcIndex, src; srcIndex = 2; cnt = $.cls('postblock')[0]; if (!cnt) { return; } tr = cnt.parentNode; el = document.createElement('td'); el.className = 'postblock'; tr.insertBefore(el, tr.children[srcIndex].nextElementSibling); cnt = $.cls('flashListing')[0]; if (!cnt) { return; } nodes = $.tag('tr', cnt); for (i = 1; tr = nodes[i]; ++i) { src = tr.children[srcIndex].firstElementChild; el = document.createElement('td'); el.innerHTML = '[Embed]'; el.firstElementChild.addEventListener('click', SWFEmbed.embedIndex, false); tr.insertBefore(el, tr.children[srcIndex].nextElementSibling); } }; SWFEmbed.toggleThread = function(e) { var cnt, link, el, post, maxWidth, ratio, width, height; if (cnt = $.id('swf-embed')) { cnt.parentNode.removeChild(cnt); e.target.textContent = 'Embed'; return; } link = $.tag('a', e.target.parentNode)[0]; maxWidth = document.documentElement.clientWidth - (!Main.hasMobileLayout ? 100 : 30); width = +link.getAttribute('data-width'); height = +link.getAttribute('data-height'); if (width > maxWidth) { ratio = width / height; width = maxWidth; height = Math.round(maxWidth / ratio); } cnt = document.createElement('div'); cnt.id = 'swf-embed'; el = SWFEmbed.getFrameNode(link.href, width, height); cnt.appendChild(el); post = $.id('m' + Main.tid); post.insertBefore(cnt, post.firstChild); $.cls('thread')[0].scrollIntoView(true); e.target.textContent = 'Remove'; }; SWFEmbed.embedIndex = function(e) { var el, cnt, header, icon, backdrop, width, height, cntWidth, cntHeight, maxWidth, maxHeight, docWidth, docHeight, margins, headerHeight, fileName, ratio; e.preventDefault(); margins = 10; headerHeight = 20; el = e.target.parentNode.parentNode.children[2].firstElementChild; fileName = el.getAttribute('title') || el.textContent; cntWidth = width = +el.getAttribute('data-width'); cntHeight = height = +el.getAttribute('data-height'); docWidth = document.documentElement.clientWidth; docHeight = document.documentElement.clientHeight; maxWidth = docWidth - margins; maxHeight = docHeight - margins - headerHeight; ratio = width / height; if (cntWidth > maxWidth) { cntWidth = maxWidth; cntHeight = Math.round(maxWidth / ratio); } if (cntHeight > maxHeight) { cntHeight = maxHeight; cntWidth = Math.round(maxHeight * ratio); } el = SWFEmbed.getFrameNode(e.target.href, cntWidth, cntHeight); cnt = document.createElement('div'); cnt.style.position = 'fixed'; cnt.style.width = cntWidth + 'px'; cnt.style.height = cntHeight + 'px'; cnt.style.top = '50%'; cnt.style.left = '50%'; cnt.style.marginTop = (-cntHeight / 2 - headerHeight / 2) + 'px'; cnt.style.marginLeft = (-cntWidth / 2) + 'px'; cnt.style.background = 'white'; header = document.createElement('div'); header.id = 'swf-embed-header'; header.className = 'postblock'; header.textContent = fileName + ', ' + width + 'x' + height; icon = document.createElement('img'); icon.id = 'swf-embed-close'; icon.className = 'pointer'; icon.src = Main.icons.cross; header.appendChild(icon); cnt.appendChild(header); cnt.appendChild(el); backdrop = document.createElement('div'); backdrop.id = 'swf-embed'; backdrop.style.cssText = 'width: 100%; height: 100%; position: fixed;\ top: 0; left: 0; background: rgba(128, 128, 128, 0.5)'; backdrop.appendChild(cnt); backdrop.addEventListener('click', SWFEmbed.onBackdropClick, false); document.body.appendChild(backdrop); }; SWFEmbed.getFrameNode = function(file_url, width, height) { var el, filename; filename = file_url.replace(/^https:\/\/i\.4cdn\.org\/f\//, ''); el = document.createElement('iframe'); el.setAttribute('allow', 'autoplay; fullscreen'); el.setAttribute('sandbox', 'allow-scripts allow-same-origin'); el.setAttribute('scrolling', 'no'); el.setAttribute('frameborder', '0'); el.setAttribute('width', +width); el.setAttribute('height', +height); el.src = `//s.4cdn.org/media/flash/embed.html?4#${+width},${+height},${filename},1`; return el; }; SWFEmbed.onBackdropClick = function(e) { var backdrop = $.id('swf-embed'); if (e.target === backdrop || e.target.id == 'swf-embed-close') { backdrop.removeEventListener('click', SWFEmbed.onBackdropClick, false); backdrop.parentNode.removeChild(backdrop); } }; /** * Linkify */ var Linkify = { init: function() { this.probeRe = /(?:^|[^\B"])https?:\/\/[-.a-z0-9]+\.[a-z]{2,4}/; this.linkRe = /(^|[^\B"])(https?:\/\/[-.a-z0-9\u200b]+\.[a-z\u200b]{2,15}(?:\/[^\s<>]*)?)/ig; this.punct = /[:!?,.'"]+$/g; this.derefer = '//sys.' + $L.d(Main.board) + '/derefer?url='; }, exec: function(el) { if (!this.probeRe.test(el.innerHTML)) { return; } el.innerHTML = el.innerHTML .replace(//g, '\u200b') .replace(this.linkRe, this.funk) .replace(/\u200b/g, ''); }, funk: function(match, pfx, url, o, str) { var m, mm, end, len, sfx; m = o + match.length; if (str.slice(m, m + 4) === '') { return match; } end = len = url.length; if (m = url.match(Linkify.punct)) { end -= m[0].length; } if (m = url.match(/\)+$/g)) { mm = m[0].length; if (m = url.match(/\(/g)) { mm = mm - m.length; if (mm > 0) { end -= mm; } } else { end -= mm; } } if (end < len) { sfx = url.slice(end); url = url.slice(0, end); } else { sfx = ''; } return pfx + '' + url + '' + sfx; } }; /** * Media */ var Media = {}; Media.init = function() { this.matchSC = /(?:soundcloud\.com|snd\.sc)\/[^\s<]+(?:)?[^\s<]*/g; this.matchYT = /(?:youtube\.com\/watch\?[^\s]*?v=|youtu\.be\/)[^\s<]+(?:)?[^\s<]*(?:)?[^\s<]*(?:)?[^\s<]*/g; this.toggleYT = /(?:v=|\.be\/)([a-zA-Z0-9_-]{11})/; this.timeYT = /[\?#&]t=([ms0-9]+)/; this.map = { yt: this.toggleYouTube, sc: this.toggleSoundCloud, }; }; Media.parseSoundCloud = function(msg) { msg.innerHTML = msg.innerHTML.replace(this.matchSC, this.replaceSoundCloud); }; Media.replaceSoundCloud = function(link, o, str) { var pfx; if (Config.linkify) { if (str[o + link.length - 1] === '"') { return link; } else { pfx = link + ''; } } else { pfx = '' + link + ''; } return pfx + ' [Embed]'; }; Media.toggleSoundCloud = function(node) { var xhr, url; if (node.textContent == 'Remove') { node.parentNode.removeChild(node.nextElementSibling); node.textContent = 'Embed'; } else if (node.textContent == 'Embed') { url = node.previousElementSibling.textContent; xhr = new XMLHttpRequest(); xhr.open('GET', '//soundcloud.com/oembed?show_artwork=false&' + 'maxwidth=500px&show_comments=false&format=json&url=' + 'http://' + url.replace(/^https?:\/\//i, '')); xhr.onload = function() { var el; if (this.status == 200 || this.status == 304) { el = document.createElement('div'); el.className = 'media-embed'; el.innerHTML = JSON.parse(this.responseText).html; node.parentNode.insertBefore(el, node.nextElementSibling); node.textContent = 'Remove'; } else { node.textContent = 'Error'; console.log('SoundCloud Error (HTTP ' + this.status + ')'); } }; node.textContent = 'Loading...'; xhr.send(null); } }; Media.parseYouTube = function(msg) { msg.innerHTML = msg.innerHTML.replace(this.matchYT, this.replaceYouTube); }; Media.replaceYouTube = function(link, o, str) { var pfx; if (Config.linkify) { if (str[o + link.length - 1] === '"') { return link; } else { pfx = link + ''; } } else { pfx = '' + link + ''; } return pfx + ' [' + (!Main.hasMobileLayout ? 'Embed' : 'Open') + ']'; }; Media.showYTPreview = function(link) { var cnt, img, vid, aabb, x, y, tw, th, pad; tw = 320; th = 180; pad = 5; aabb = link.getBoundingClientRect(); vid = link.previousElementSibling.textContent.match(this.toggleYT)[1]; if (aabb.right + tw + pad > $.docEl.clientWidth) { x = aabb.left - tw - pad; } else { x = aabb.right + pad; } y = aabb.top - th / 2 + aabb.height / 2; img = document.createElement('img'); img.width = tw; img.height = th; img.alt = ''; img.src = '//i1.ytimg.com/vi/' + encodeURIComponent(vid) + '/mqdefault.jpg'; cnt = document.createElement('div'); cnt.id = 'yt-preview'; cnt.className = 'reply'; cnt.style.left = (x + window.pageXOffset) + 'px'; cnt.style.top = (y + window.pageYOffset) + 'px'; cnt.appendChild(img); document.body.appendChild(cnt); }; Media.removeYTPreview = function() { var el; if (el = $.id('yt-preview')) { document.body.removeChild(el); } }; Media.toggleYouTube = function(node) { var vid, time, el, url; if (node.textContent == 'Remove') { node.parentNode.removeChild(node.nextElementSibling); node.textContent = 'Embed'; } else { url = node.previousElementSibling.textContent; vid = url.match(this.toggleYT); time = url.match(this.timeYT); if (vid && (vid = vid[1])) { vid = encodeURIComponent(vid); if (time && (time = time[1])) { vid += '?start=' + encodeURIComponent(time); } if (Main.hasMobileLayout) { window.open('//www.youtube.com/watch?v=' + vid); return; } el = document.createElement('div'); el.className = 'media-embed'; el.innerHTML = ''; node.parentNode.insertBefore(el, node.nextElementSibling); node.textContent = 'Remove'; } else { node.textContent = 'Error'; } } }; Media.toggleEmbed = function(node) { var fn, type = node.getAttribute('data-type'); if (type && (fn = Media.map[type])) { fn.call(this, node); } }; /** * Sticky nav auto-hide */ var StickyNav = { thres: 5, pos: 0, timeout: null, el: null, init: function() { this.el = Config.classicNav ? $.id('boardNavDesktop') : $.id('boardNavMobile'); $.addClass(this.el, 'autohide-nav'); window.addEventListener('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; } }; /** * Custom CSS */ var CustomCSS = {}; CustomCSS.init = function() { var style, css; if (css = localStorage.getItem('4chan-css')) { style = document.createElement('style'); style.id = 'customCSS'; style.setAttribute('type', 'text/css'); style.textContent = css; document.head.appendChild(style); } }; CustomCSS.open = function() { var cnt, ta, data; if ($.id('customCSSMenu')) { return; } cnt = document.createElement('div'); cnt.id = 'customCSSMenu'; cnt.className = 'UIPanel'; cnt.setAttribute('data-cmd', 'css-close'); cnt.innerHTML = '\
    Custom CSS\ Close
    \ \
    \
    '; document.body.appendChild(cnt); cnt.addEventListener('click', this.onClick, false); ta = $.id('customCSSBox'); if (data = localStorage.getItem('4chan-css')) { ta.textContent = data; } ta.focus(); }; CustomCSS.save = function() { var ta, style; if (ta = $.id('customCSSBox')) { localStorage.setItem('4chan-css', ta.value); if (Config.customCSS && (style = $.id('customCSS'))) { document.head.removeChild(style); CustomCSS.init(); } } }; CustomCSS.close = function() { var cnt; if (cnt = $.id('customCSSMenu')) { cnt.removeEventListener('click', this.onClick, false); document.body.removeChild(cnt); } }; CustomCSS.onClick = function(e) { var cmd; if (cmd = e.target.getAttribute('data-cmd')) { switch (cmd) { case 'css-close': CustomCSS.close(); break; case 'css-save': CustomCSS.save(); CustomCSS.close(); break; } } }; /** * Keyboard shortcuts */ var Keybinds = { enabled: false }; Keybinds.init = function() { this.enabled = true; this.map = { // A 65: function() { if (ThreadUpdater.enabled) ThreadUpdater.toggleAuto(); }, // F 70: function() { if (Config.filter) { Filter.addSelection(); } }, // Q 81: function() { if (QR.enabled && Main.tid) { QR.quotePost(Main.tid); } }, // R 82: function() { if (ThreadUpdater.enabled) ThreadUpdater.forceUpdate(); }, // W 87: function() { if (Config.threadWatcher && Main.tid) ThreadWatcher.toggle(Main.tid); }, // B 66: function() { var el; (el = $.cls('prev')[0]) && (el = $.tag('form', el)[0]) && el.submit(); }, // C 67: function() { location.href = '/' + Main.board + '/catalog'; }, // N 78: function() { var el; (el = $.cls('next')[0]) && (el = $.tag('form', el)[0]) && el.submit(); }, // I 73: function() { location.href = '/' + Main.board + '/'; } }; document.addEventListener('keydown', this.resolve, false); }; Keybinds.resolve = function(e) { var bind, el = e.target; if (!Keybinds.enabled || el.nodeName == 'TEXTAREA' || el.nodeName == 'INPUT') { return; } bind = Keybinds.map[e.keyCode]; if (bind && !e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) { e.preventDefault(); e.stopPropagation(); bind(); } }; Keybinds.open = function() { var cnt; if ($.id('keybindsHelp')) { return; } cnt = document.createElement('div'); cnt.id = 'keybindsHelp'; cnt.className = 'UIPanel'; cnt.setAttribute('data-cmd', 'keybinds-close'); cnt.innerHTML = '\
    Keyboard Shortcuts\ Close
    \ '; document.body.appendChild(cnt); cnt.addEventListener('click', this.onClick, false); }; Keybinds.close = function() { var cnt; if (cnt = $.id('keybindsHelp')) { cnt.removeEventListener('click', this.onClick, false); document.body.removeChild(cnt); } }; Keybinds.onClick = function(e) { var cmd; if ((cmd = e.target.getAttribute('data-cmd')) && cmd == 'keybinds-close') { Keybinds.close(); } }; var Del = { deletePost: function(pid, file_only) { var params; if (!confirm('Delete ' + (file_only ? 'file' : 'post') + '?')) { return; } params = { mode: window.thread_archived ? 'arcdel' : 'usrdel' }; params[pid] = 'delete'; if (file_only) { params['onlyimgdel'] = 'on'; } $.xhr('POST', 'https://sys.' + $L.d(Main.board) + '/' + Main.board + '/imgboard.php', { onload: Del.onPostDeleted, onerror: Del.onError, withCredentials: true, pid: pid, file_only: file_only }, params ); }, onPostDeleted: function() { var el; if (!this.file_only) { el = $.id('pc' + this.pid); } else if (el = $.id('f' + this.pid)) { el = $.tag('img', el)[0]; if (!el.hasAttribute('data-md5')) { return; } } if (!el) { return; } if (/Updating index|Can't find the post/.test(this.responseText)) { if (!$.hasClass(el, 'deleted')) { $.addClass(el, 'deleted'); } } else { Del.onError(); } }, onError: function() { Feedback.error('Something went wrong.'); } }; /** * Reporting */ var Report = { init: function() { window.addEventListener('message', Report.onMessage, false); } }; Report.onMessage = function(e) { var id; if (e.origin === ('https://sys.' + $L.d(Main.board)) && /^done-report/.test(e.data)) { id = e.data.split('-')[2]; if (Config.threadHiding && $.id('t' + id)) { if (!ThreadHiding.isHidden(id)) { ThreadHiding.hide(id); ThreadHiding.save(); } return; } if ($.id('p' + id)) { if (!ReplyHiding.isHidden(id)) { ReplyHiding.hide(id); ReplyHiding.save(); } return; } } }; Report.open = function(pid, board) { var height, altc; if (QR.noCaptcha) { height = 205; altc = ''; } else { height = 510; altc = ''; } window.open('https://sys.' + $L.d(Main.board) + '/' + (board || Main.board) + '/imgboard.php?mode=report&no=' + pid + altc, Date.now(), "toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,width=380,height=" + height); }; /** * Custom Menu */ var CustomMenu = {}; CustomMenu.initCtrl = function() { var el, cnt; el = document.createElement('span'); el.className = 'custom-menu-ctrl'; el.innerHTML = '[Edit]'; if (Config.dropDownNav && !Config.classicNav && !Main.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 (Config.dropDownNav && !Config.classicNav && !Main.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 + '/'; cnt.appendChild(el); } cnt.appendChild(document.createTextNode(']')); if (Config.dropDownNav && !Config.classicNav && !Main.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; cnt = document.createElement('div'); cnt.id = 'customMenu'; cnt.className = 'UIPanel'; cnt.setAttribute('data-close', '1'); if (standalone === true) { cnt.setAttribute('data-standalone', '1'); } cnt.innerHTML = '\
    Custom Board List\ Close
    \ \
    '; document.body.appendChild(cnt); if (Config.customMenuList) { $.id('customMenuBox').value = Config.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); } }; CustomMenu.save = function(standalone) { var input; if (input = $.id('customMenuBox')) { Config.customMenuList = input.value; if (standalone === true) { CustomMenu.apply(Config.customMenuList); Config.customMenu = true; Config.save(); } } CustomMenu.closeEditor(); }; /** * Draggable helper */ var Draggable = { el: null, key: null, scrollX: null, scrollY: null, dx: null, dy: null, right: null, bottom: null, offsetTop: 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 = Main.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) { Config[Draggable.key] = Draggable.el.style.cssText; Config.save(); } 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 = ''; } } }; /** * User Agent */ var UA = {}; UA.init = function() { document.head = document.head || $.tag('head')[0]; this.isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; this.hasCORS = 'withCredentials' in new XMLHttpRequest(); this.hasFormData = 'FormData' in window; }; UA.dispatchEvent = function(name, detail) { var e = document.createEvent('Event'); e.initEvent(name, false, false); if (detail) { e.detail = detail; } document.dispatchEvent(e); }; UA.getSelection = function(raw) { var sel; if (UA.isOpera && typeof (sel = document.getSelection()) == 'string') {} else { sel = window.getSelection(); if (!raw) { sel = sel.toString(); } } return sel; }; /** * Config */ var Config = { quotePreview: true, backlinks: true, quickReply: true, threadUpdater: true, threadHiding: true, alwaysAutoUpdate: false, topPageNav: false, threadWatcher: false, threadAutoWatcher: false, imageExpansion: true, fitToScreenExpansion: false, threadExpansion: true, alwaysDepage: false, localTime: true, stickyNav: false, keyBinds: false, inlineQuotes: false, //showNWSBoards: false, filter: false, revealSpoilers: false, imageHover: false, threadStats: true, IDColor: true, noPictures: false, embedYouTube: true, embedSoundCloud: false, updaterSound: false, customCSS: false, autoScroll: false, hideStubs: false, compactThreads: false, centeredThreads: false, dropDownNav: false, autoHideNav: false, classicNav: false, fixedThreadWatcher: false, persistentQR: false, forceHTTPS: false, darkTheme: false, linkify: false, unmuteWebm: false, disableAll: false }; var ConfigMobile = { embedYouTube: false, compactThreads: false, linkify: true, }; Config.load = function() { var storage; if (storage = localStorage.getItem('4chan-settings')) { storage = JSON.parse(storage); $.extend(Config, storage); if (Main.getCookie('https') === '1') { Config.forceHTTPS = true; } else { Config.forceHTTPS = false; } } else { Main.firstRun = true; } }; Config.loadFromURL = function() { var cmd, data; cmd = location.href.split('=', 2); if (/#cfg$/.test(cmd[0])) { try { data = JSON.parse(decodeURIComponent(cmd[1])); history.replaceState(null, '', location.href.split('#', 1)[0]); $.extend(Config, JSON.parse(data.settings)); Config.save(); if (data.filters) { localStorage.setItem('4chan-filters', data.filters); } if (data.css) { localStorage.setItem('4chan-css', data.css); } if (data.catalogFilters) { localStorage.setItem('catalog-filters', data.catalogFilters); } if (data.catalogSettings) { localStorage.setItem('catalog-settings', data.catalogSettings); } return true; } catch (e) { console.log(e); } } return false; }; Config.toURL = function() { var data, cfg = {}; cfg.settings = localStorage.getItem('4chan-settings'); if (data = localStorage.getItem('4chan-filters')) { cfg.filters = data; } if (data = localStorage.getItem('4chan-css')) { cfg.css = data; } if (data = localStorage.getItem('catalog-filters')) { cfg.catalogFilters = data; } if (data = localStorage.getItem('catalog-settings')) { cfg.catalogSettings = data; } return encodeURIComponent(JSON.stringify(cfg)); }; Config.save = function(old) { localStorage.setItem('4chan-settings', JSON.stringify(Config)); //StorageSync.sync('4chan-settings'); if (!old) { return; } if (Config.forceHTTPS) { Main.setCookie('https', 1); } else { Main.removeCookie('https'); } if (old.darkTheme != Config.darkTheme) { if (Config.darkTheme) { Main.setCookie('nws_style', 'Tomorrow', '.' + $L.d(Main.board)); Main.setCookie('ws_style', 'Tomorrow', '.' + $L.d(Main.board)); } else { Main.removeCookie('nws_style', '.' + $L.d(Main.board)); Main.removeCookie('ws_style', '.' + $L.d(Main.board)); } } }; /** * Settings menu */ var SettingsMenu = {}; // [ Name, Subtitle, available on mobile?, is sub-option?, is mobile only? ] SettingsMenu.options = { 'Quotes & Replying': { quotePreview: [ 'Quote preview', 'Show post when mousing over post links', true ], backlinks: [ 'Backlinks', 'Show who has replied to a post', true ], inlineQuotes: [ 'Inline quote links', 'Clicking quote links will inline expand the quoted post, Shift-click to bypass inlining' ], quickReply: [ 'Quick Reply', 'Quickly respond to a post by clicking its post number', true ], persistentQR: [ 'Persistent Quick Reply', 'Keep Quick Reply window open after posting' ], }, 'Monitoring': { threadUpdater: [ 'Thread updater', 'Append new posts to bottom of thread without refreshing the page', true ], alwaysAutoUpdate:[ 'Auto-update by default', 'Always auto-update threads', true ], threadWatcher: [ 'Thread Watcher', 'Keep track of threads you\'re watching and see when they receive new posts', true ], threadAutoWatcher: [ 'Automatically watch threads you create', '', true, true ], autoScroll: [ 'Auto-scroll with auto-updated posts', 'Automatically scroll the page as new posts are added' ], updaterSound: [ 'Sound notification', 'Play a sound when somebody replies to your post(s)' ], fixedThreadWatcher: [ 'Pin Thread Watcher to the page', 'Thread Watcher will scroll with you' ], threadStats: [ 'Thread statistics', 'Display post and image counts on the right of the page, italics signify bump/image limit has been met', true ] }, 'Filters & Post Hiding': { filter: [ 'Filter and highlight specific threads/posts [Edit]', 'Enable pattern-based filters' ], threadHiding: [ 'Thread hiding [Clear History]', 'Hide entire threads by clicking the minus button', true ], hideStubs: [ 'Hide thread stubs', "Don't display stubs of hidden threads" ] }, 'Navigation': { threadExpansion: [ 'Thread expansion', 'Expand threads inline on board indexes', true ], dropDownNav: [ 'Use persistent drop-down navigation bar', '' ], classicNav: [ 'Use traditional board list', '', false, true ], autoHideNav: [ 'Auto-hide on scroll', '', false, true ], customMenu: [ 'Custom board list [Edit]', 'Only show selected boards in top and bottom board lists' ], //showNWSBoards: [ 'Show all boards', 'Show all boards in top and bottom board lists on 4channel.org', true], alwaysDepage: [ 'Always use infinite scroll', 'Enable infinite scroll by default, so reaching the bottom of the board index will load subsequent pages', true ], topPageNav: [ 'Page navigation at top of page', 'Show the page switcher at the top of the page, hold Shift and drag to move' ], stickyNav: [ 'Navigation arrows', 'Show top and bottom navigation arrows, hold Shift and drag to move' ], keyBinds: [ 'Use keyboard shortcuts [Show]', 'Enable handy keyboard shortcuts for common actions' ] }, 'Images & Media': { imageExpansion: [ 'Image expansion', 'Enable inline image expansion, limited to browser width', true ], fitToScreenExpansion: [ 'Fit expanded images to screen', 'Limit expanded images to both browser width and height' ], imageHover: [ 'Image hover', 'Mouse over images to view full size, limited to browser size' ], imageHoverBg: [ 'Set a background color for transparent images', '', false, true ], revealSpoilers: [ "Don't spoiler images", 'Show image thumbnail and original filename instead of spoiler placeholders', true ], unmuteWebm: [ 'Un-mute WebM audio', 'Un-mute sound automatically for WebM playback', true ], noPictures: [ 'Hide thumbnails', 'Don\'t display thumbnails while browsing', true ], embedYouTube: [ 'Embed YouTube links', 'Embed YouTube player into replies' ], embedSoundCloud: [ 'Embed SoundCloud links', 'Embed SoundCloud player into replies' ], }, 'Miscellaneous': { linkify: [ 'Linkify URLs', 'Make user-posted links clickable', true ], darkTheme: [ 'Use a dark theme', 'Use the Tomorrow theme for nighttime browsing', true, false, true ], customCSS: [ 'Custom CSS [Edit]', 'Include your own CSS rules', true ], IDColor: [ 'Color user IDs', 'Assign unique colors to user IDs on boards that use them', true ], compactThreads: [ 'Force long posts to wrap', 'Long posts will wrap at 75% browser width' ], centeredThreads: [ 'Center threads', 'Align threads to the center of page', false ], localTime: [ 'Convert dates to local time', 'Convert 4chan server time (US Eastern Time) to your local time', true ], forceHTTPS: [ 'Always use HTTPS', 'Rewrite 4chan URLs to always use HTTPS', true ] } }; SettingsMenu.save = function() { var i, options, el, key, old; old = {}; $.extend(old, Config); options = $.id('settingsMenu').getElementsByClassName('menuOption'); for (i = 0; el = options[i]; ++i) { key = el.getAttribute('data-option'); Config[key] = el.type == 'checkbox' ? el.checked : el.value; } Config.save(old); UA.dispatchEvent('4chanSettingsSaved'); SettingsMenu.close(); location.href = location.href.replace(/#.+$/, ''); }; SettingsMenu.toggle = function() { if ($.id('settingsMenu')) { SettingsMenu.close(); } else { SettingsMenu.open(); } }; SettingsMenu.open = function() { var i, cat, categories, key, html, cnt, opts, mobileOpts, el; if (Main.firstRun) { if (el = $.id('settingsTip')) { el.parentNode.removeChild(el); } if (el = $.id('settingsTipBottom')) { el.parentNode.removeChild(el); } Config.save(); } cnt = document.createElement('div'); cnt.id = 'settingsMenu'; cnt.className = 'UIPanel'; html = '
    Settings' + 'Close' + '
      '; html += ''; if (Main.hasMobileLayout) { categories = {}; for (cat in SettingsMenu.options) { mobileOpts = {}; opts = SettingsMenu.options[cat]; for (key in opts) { if (opts[key][2]) { mobileOpts[key] = opts[key]; } } for (i in mobileOpts) { categories[cat] = mobileOpts; break; } } } else { categories = SettingsMenu.options; } for (cat in categories) { opts = categories[cat]; html += '
      • ' + '' + '' + cat + '
        • '; for (key in opts) { // Mobile layout only? if (opts[key][4] && !Main.hasMobileLayout) { continue; } html += '' : '>') + '' + (opts[key][1] !== false ? '
        • ' : '">') + opts[key][1] : '') + '
        • '; } html += '
      '; } html += '
    • ' + '
    ' + '
    ' + '
    '; cnt.innerHTML = html; cnt.addEventListener('click', SettingsMenu.onClick, false); document.body.appendChild(cnt); if (Main.firstRun) { SettingsMenu.expandAll(); } (el = $.cls('menuOption', cnt)[0]) && el.focus(); }; SettingsMenu.showExport = function() { var cnt, str, el; if ($.id('exportSettings')) { return; } str = location.href.replace(location.hash, '').replace(/^http:/, 'https:') + '#cfg=' + Config.toURL(); cnt = document.createElement('div'); cnt.id = 'exportSettings'; cnt.className = 'UIPanel'; cnt.setAttribute('data-cmd', 'export-close'); cnt.innerHTML = '\
    Export Settings\ Close
    \

    Copy and save the URL below, and visit it from another \ browser or computer to restore your extension and catalog settings.

    \

    \

    \

    Alternatively, you can drag the link below into your \ bookmarks bar and click it to restore.

    \

    [Restore 4chan Settings]

    '; document.body.appendChild(cnt); cnt.addEventListener('click', this.onExportClick, false); el = $.cls('export-field', cnt)[0]; el.focus(); el.select(); }; SettingsMenu.closeExport = function() { var cnt; if (cnt = $.id('exportSettings')) { cnt.removeEventListener('click', this.onExportClick, false); document.body.removeChild(cnt); } }; SettingsMenu.onExportClick = function(e) { if (e.target.id == 'exportSettings') { e.preventDefault(); e.stopPropagation(); SettingsMenu.closeExport(); } }; SettingsMenu.expandAll = function() { var i, el, nodes = $.cls('settings-expand'); for (i = 0; el = nodes[i]; ++i) { el.src = Main.icons.minus; el.parentNode.nextElementSibling.style.display = 'block'; } }; SettingsMenu.toggleCat = function(t) { var icon, disp, el = t.parentNode.nextElementSibling; if (!el.style.display) { disp = 'block'; icon = 'minus'; } else { disp = ''; icon = 'plus'; } el.style.display = disp; t.parentNode.firstElementChild.src = Main.icons[icon]; }; SettingsMenu.onClick = function(e) { var el, t; t = e.target; if ($.hasClass(t, 'settings-expand')) { SettingsMenu.toggleCat(t); } else if (t.getAttribute('data-cmd') == 'settings-exp-all') { e.preventDefault(); SettingsMenu.expandAll(); } else if (t.id == 'settingsMenu' && (el = $.id('settingsMenu'))) { e.preventDefault(); SettingsMenu.close(el); } }; SettingsMenu.close = function(el) { if (el = (el || $.id('settingsMenu'))) { el.removeEventListener('click', SettingsMenu.onClick, false); document.body.removeChild(el); } }; /** * Feedback */ var Feedback = { messageTimeout: null, showMessage: function(msg, type, timeout, onClick) { var el; Feedback.hideMessage(); el = document.createElement('div'); el.id = 'feedback'; el.title = 'Dismiss'; el.innerHTML = ''; $.on(el, 'click', onClick || Feedback.hideMessage); document.body.appendChild(el); if (timeout) { Feedback.messageTimeout = setTimeout(Feedback.hideMessage, timeout); } }, hideMessage: function() { var el = $.id('feedback'); if (el) { if (Feedback.messageTimeout) { clearTimeout(Feedback.messageTimeout); Feedback.messageTimeout = null; } $.off(el, 'click', Feedback.hideMessage); document.body.removeChild(el); } }, error: function(msg, timeout) { if (timeout === undefined) { timeout = 5000; } Feedback.showMessage(msg || 'Something went wrong', 'error', timeout); }, notify: function(msg, timeout) { if (timeout === undefined) { timeout = 3000; } Feedback.showMessage(msg, 'notify', timeout); } }; /** * Main */ var Main = {}; Main.addTooltip = function(link, message, id) { var el, pos; el = document.createElement('div'); el.className = 'click-me'; if (id) { el.id = id; } el.innerHTML = message || 'Change your settings'; link.parentNode.appendChild(el); pos = (link.offsetWidth - el.offsetWidth + link.offsetLeft - el.offsetLeft) / 2; el.style.marginLeft = pos + 'px'; return el; }; Main.getDocTopOffset = function() { if (Config.dropDownNav && !Config.autoHideNav) { return $.id( Config.classicNav ? 'boardNavDesktop' : 'boardNavMobile' ).offsetHeight; } else { return 0; } }; Main.init = function() { var params; document.addEventListener('DOMContentLoaded', Main.run, false); Main.now = Date.now(); Main.is_4channel = location.host === 'boards.4channel.org'; UA.init(); Config.load(); if (Config.forceHTTPS && location.protocol != 'https:') { location.href = location.href.replace(/^http:/, 'https:'); return; } if (Main.firstRun && Config.loadFromURL()) { Main.firstRun = false; } if (Main.stylesheet = Main.getCookie(style_group)) { Main.stylesheet = Main.stylesheet.toLowerCase().replace(/ /g, '_'); } else { Main.stylesheet = style_group == 'nws_style' ? 'yotsuba_new' : 'yotsuba_b_new'; } QR.noCaptcha = QR.noCaptcha || window.passEnabled; Main.initIcons(); Main.addCSS(); Main.type = style_group.split('_')[0]; params = location.pathname.split(/\//); Main.board = params[1]; Main.page = params[2]; Main.tid = params[3]; UA.dispatchEvent('4chanMainInit'); }; Main.initPersistentNav = function() { var el, top, bottom; top = $.id('boardNavDesktop'); bottom = $.id('boardNavDesktopFoot'); if (Config.classicNav) { el = document.createElement('div'); el.className = 'pageJump'; el.innerHTML = '' + 'Settings' + 'Home
    '; top.appendChild(el); $.id('settingsWindowLinkClassic') .addEventListener('click', SettingsMenu.toggle, false); $.addClass(top, 'persistentNav'); } else { top.style.display = 'none'; $.removeClass($.id('boardNavMobile'), 'mobile'); } bottom.style.display = 'none'; $.addClass(document.body, 'hasDropDownNav'); }; Main.checkMobileLayout = function() { 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; }; Main.disableDarkTheme = function() { Config.darkTheme = false; localStorage.setItem('4chan-settings', JSON.stringify(Config)); }; Main.run = function() { var el, thread; document.removeEventListener('DOMContentLoaded', Main.run, false); document.addEventListener('click', Main.onclick, false); $.id('settingsWindowLink').addEventListener('click', SettingsMenu.toggle, false); $.id('settingsWindowLinkBot').addEventListener('click', SettingsMenu.toggle, false); $.id('settingsWindowLinkMobile').addEventListener('click', SettingsMenu.toggle, false); Main.isOekakiBoard = Main.board === 'i'; Main.hasMobileLayout = Main.checkMobileLayout(); Main.isMobileDevice = /Mobile|Android|Dolfin|Opera Mobi|PlayStation Vita|Nintendo DS/.test(navigator.userAgent); Search.init(); if (Config.disableAll) { return; } Report.init(); if (Main.hasMobileLayout) { $.extend(Config, ConfigMobile); } else { if (el = $.id('bottomReportBtn')) { el.style.display = 'none'; } if (Main.isMobileDevice) { $.addClass(document.body, 'isMobileDevice'); } } /* if (Main.is_4channel && Config.showNWSBoards) { CustomMenu.showNWSBoards(); } */ if (Config.linkify) { Linkify.init(); } if (Config.IDColor) { IDColor.init(); } if (Config.customCSS) { CustomCSS.init(); } if (Config.keyBinds) { Keybinds.init(); } if (Main.firstRun && Main.isMobileDevice) { Config.topPageNav = false; Config.dropDownNav = true; } if (Config.dropDownNav && !Main.hasMobileLayout) { Main.initPersistentNav(); } $.addClass(document.body, Main.stylesheet); $.addClass(document.body, Main.type); if (Config.darkTheme) { $.addClass(document.body, 'm-dark'); if (!Main.hasMobileLayout) { $.cls('stylechanger')[0].addEventListener('change', Main.disableDarkTheme, false); } } if (Config.compactThreads) { $.addClass(document.body, 'compact'); } else if (Config.centeredThreads) { $.addClass(document.body, 'centeredThreads'); } if (Config.noPictures) { $.addClass(document.body, 'noPictures'); } if (Config.customMenu) { CustomMenu.apply(Config.customMenuList); } CustomMenu.initCtrl(); if (Config.quotePreview || Config.imageHover|| Config.filter) { thread = $.id('delform') || $.id('arc-list'); thread.addEventListener('mouseover', Main.onThreadMouseOver, false); thread.addEventListener('mouseout', Main.onThreadMouseOut, false); } if (Config.stickyNav) { Main.setStickyNav(); } if (!Main.hasMobileLayout) { Main.initGlobalMessage(); if (Config.autoHideNav) { StickyNav.init(); } } else { StickyNav.init(); } if (Config.threadExpansion) { ThreadExpansion.init(); } if (Config.filter) { Filter.init(); } if (Config.threadWatcher) { ThreadWatcher.init(); } if (Main.hasMobileLayout || Config.embedSoundCloud || Config.embedYouTube) { Media.init(); } ReplyHiding.init(); if (Config.quotePreview) { QuotePreview.init(); } Parser.init(); if (Main.tid) { Main.threadClosed = !document.forms.post || !!$.cls('closedIcon')[0]; Main.threadSticky = !!$.cls('stickyIcon', $.id('pi' + Main.tid))[0]; if (Config.threadStats) { ThreadStats.init(); } Parser.parseThread(Main.tid); if (Config.threadUpdater) { ThreadUpdater.init(); } } else { if (!Main.page) { Depager.init(); } if (Config.topPageNav) { Main.setPageNav(); } if (Config.threadHiding) { ThreadHiding.init(); Parser.parseBoard(); } else { Parser.parseBoard(); } } if (Main.board === 'f') { SWFEmbed.init(); } if (Config.quickReply) { QR.init(); } ReplyHiding.purge(); if (Config.alwaysDepage && !Main.hasMobileLayout) { if ($.docEl.scrollHeight <= $.docEl.clientHeight) { Depager.depage(); } } }; Main.isThreadClosed = function(tid) { var el; return window.thread_archived || ((el = $.id('pi' + tid)) && $.cls('closedIcon', el)[0]); }; Main.setThreadState = function(state, mode) { var cnt, el, ref, cap; cap = state.charAt(0).toUpperCase() + state.slice(1); if (mode) { cnt = $.cls('postNum', $.id('pi' + Main.tid))[0]; el = document.createElement('img'); el.className = state + 'Icon retina'; el.title = cap; el.src = Main.icons2[state]; if (state == 'sticky' && (ref = $.cls('closedIcon', cnt)[0])) { cnt.insertBefore(el, ref); cnt.insertBefore(document.createTextNode(' '), ref); } else { cnt.appendChild(document.createTextNode(' ')); cnt.appendChild(el); } } else { if (el = $.cls(state + 'Icon', $.id('pi' + Main.tid))[0]) { el.parentNode.removeChild(el.previousSibling); el.parentNode.removeChild(el); } } Main['thread' + cap] = mode; }; Main.icons = { up: 'arrow_up.png', down: 'arrow_down.png', right: 'arrow_right.png', download: 'arrow_down2.png', refresh: 'refresh.png', cross: 'cross.png', gis: 'gis.png', iqdb: 'iqdb.png', minus: 'post_expand_minus.png', plus: 'post_expand_plus.png', rotate: 'post_expand_rotate.gif', quote: 'quote.png', report: 'report.png', notwatched: 'watch_thread_off.png', watched: 'watch_thread_on.png', help: 'question.png' }; Main.icons2 = { archived: 'archived.gif', closed: 'closed.gif', sticky: 'sticky.gif', trash: 'trash.gif' }, Main.initIcons = function() { var key, paths, url; paths = { yotsuba_new: 'futaba/', futaba_new: 'futaba/', yotsuba_b_new: 'burichan/', burichan_new: 'burichan/', tomorrow: 'tomorrow/', photon: 'photon/' }; url = '//s.4cdn.org/image/'; if (window.devicePixelRatio >= 2) { for (key in Main.icons) { Main.icons[key] = Main.icons[key].replace('.', '@2x.'); } for (key in Main.icons2) { Main.icons2[key] = Main.icons2[key].replace('.', '@2x.'); } } for (key in Main.icons2) { Main.icons2[key] = url + Main.icons2[key]; } url += 'buttons/' + paths[Main.stylesheet]; for (key in Main.icons) { Main.icons[key] = url + Main.icons[key]; } }; Main.setPageNav = function() { var el, cnt; cnt = document.createElement('div'); cnt.setAttribute('data-shiftkey', '1'); cnt.setAttribute('data-trackpos', 'TN-position'); cnt.className = 'topPageNav'; if (Config['TN-position']) { cnt.style.cssText = Config['TN-position']; } else { cnt.style.left = '10px'; cnt.style.top = '50px'; } el = $.cls('pagelist')[0]; if (!el) { return; } el = el.cloneNode(true); cnt.appendChild(el); Draggable.set(el); document.body.appendChild(cnt); }; Main.getWebmVolume = function() { let vol = parseFloat(localStorage.getItem('4chan-volume')); if (!isNaN(vol)) { return vol; } else { return 0.5; } }; Main.getWebmVolumeChangeCb = function() { let t; return (e) => { clearTimeout(t); t = setTimeout(() => { localStorage.setItem('4chan-volume', e.target.volume); }, 200); }; }; Main.initGlobalMessage = function() { var msg, btn, thisTs, oldTs; if ((msg = $.id('globalMessage')) && msg.textContent) { msg.nextElementSibling.style.clear = 'both'; btn = document.createElement('img'); btn.id = 'toggleMsgBtn'; btn.className = 'extButton'; btn.setAttribute('data-cmd', 'toggleMsg'); btn.alt = 'Toggle'; 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.src = Main.icons.plus; } else { btn.src = Main.icons.minus; } msg.parentNode.insertBefore(btn, msg); } }; Main.toggleGlobalMessage = function() { var msg, btn; msg = $.id('globalMessage'); btn = $.id('toggleMsgBtn'); if (msg.style.display == 'none') { msg.style.display = ''; btn.src = Main.icons.minus; btn.style.opacity = '1'; localStorage.removeItem('4chan-global-msg'); } else { msg.style.display = 'none'; btn.src = Main.icons.plus; btn.style.opacity = '0.5'; localStorage.setItem('4chan-global-msg', msg.getAttribute('data-utc')); } //StorageSync.sync('4chan-global-msg'); }; Main.setStickyNav = function() { var cnt, hdr; cnt = document.createElement('div'); cnt.id = 'stickyNav'; cnt.className = 'extPanel reply'; cnt.setAttribute('data-shiftkey', '1'); cnt.setAttribute('data-trackpos', 'SN-position'); if (Config['SN-position']) { cnt.style.cssText = Config['SN-position']; } else { cnt.style.right = '10px'; cnt.style.top = '50px'; } hdr = document.createElement('div'); hdr.innerHTML = '▲' + '▼'; Draggable.set(hdr); cnt.appendChild(hdr); document.body.appendChild(cnt); }; Main.getCookie = 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; }; Main.setCookie = function(name, value, domain) { var date = new Date(); date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000)); if (!domain) { domain = location.host; } document.cookie = name + '=' + value + '; expires=' + date.toGMTString() + '; path=/; domain=' + domain; }; Main.removeCookie = function(name, domain, path) { if (!domain) { domain = location.host; } if (!path) { path = '/'; } document.cookie = name + '=' + '; expires=Thu, 01 Jan 1970 00:00:01 GMT;' + '; path=' + path + '; domain=' + domain; }; Main.onclick = function(e) { var t, cmd, tid, id; if ((t = e.target) == document) { return; } if (cmd = t.getAttribute('data-cmd')) { id = t.getAttribute('data-id'); switch (cmd) { case 'update': e.preventDefault(); ThreadUpdater.forceUpdate(); break; case 'post-menu': e.preventDefault(); PostMenu.open(t); break; case 'auto': ThreadUpdater.toggleAuto(); break; case 'totop': case 'tobottom': if (!e.shiftKey) { location.href = '#' + cmd.slice(2); } break; case 'hide': ThreadHiding.toggle(id); break; case 'watch': ThreadWatcher.toggle(id); break; case 'hide-r': if (t.hasAttribute('data-recurse')) { ReplyHiding.toggleR(id); } else { ReplyHiding.toggle(id); } break; case 'expand': ThreadExpansion.toggle(id); break; case 'open-qr': e.preventDefault(); QR.show(Main.tid); $.tag('textarea', document.forms.qrPost)[0].focus(); break; case 'qr-painter-draw': QR.openPainter(); break; case 'qr-painter-clear': QR.onPainterCancel(); break; case 'qr-painter-edit': e.preventDefault(); QR.onOpenInPainterClick(t); break; case 'unfilter': Filter.unfilter(t); break; case 'depage': e.preventDefault(); Depager.toggle(); break; case 'report': Report.open(id, t.getAttribute('data-board')); break; case 'filter-sel': e.preventDefault(); Filter.addSelection(); break; case 'embed': Media.toggleEmbed(t); break; case 'sound': ThreadUpdater.toggleSound(); break; case 'toggleMsg': Main.toggleGlobalMessage(); break; case 'settings-toggle': SettingsMenu.toggle(); break; case 'settings-save': SettingsMenu.save(); break; case 'keybinds-open': Keybinds.open(); break; case 'filters-open': Filter.open(); break; case 'thread-hiding-clear': ThreadHiding.clear(); break; case 'css-open': CustomCSS.open(); break; case 'settings-export': SettingsMenu.showExport(); break; case 'export-close': SettingsMenu.closeExport(); break; case 'custom-menu-edit': e.preventDefault(); CustomMenu.showEditor($.hasClass(t.parentNode, 'custom-menu-ctrl')); break; case 'del-post': case 'del-file': e.preventDefault(); Del.deletePost(id, cmd === 'del-file'); break; case 'open-tex-preview': QR.openTeXPreview(); break; case 'close-tex-preview': QR.closeTeXPreview(); break; } } else if (!Config.disableAll) { if (QR.enabled && t.title == 'Reply to this post') { e.preventDefault(); tid = Main.tid || t.previousElementSibling.getAttribute('href').split('#')[0].split('/').slice(-1)[0]; QR.quotePost(tid, !e.ctrlKey && t.textContent); } else if (Config.imageExpansion && e.which == 1 && t.parentNode && $.hasClass(t.parentNode, 'fileThumb') && t.parentNode.nodeName == 'A' && !$.hasClass(t.parentNode, 'deleted') && !$.hasClass(t, 'mFileInfo')) { if (ImageExpansion.toggle(t)) { e.preventDefault(); } return; } else if (Config.inlineQuotes && e.which == 1 && $.hasClass(t, 'quotelink') && Main.page !== 'archive') { if (!e.shiftKey) { QuoteInline.toggle(t, e); } else { e.preventDefault(); window.location = t.href; } } else if (Config.threadExpansion && t.parentNode && $.hasClass(t.parentNode, 'abbr')) { e.preventDefault(); ThreadExpansion.expandComment(t); } else if (Main.isMobileDevice && Config.quotePreview && $.hasClass(t, 'quotelink') && t.getAttribute('href').match(QuotePreview.regex)) { e.preventDefault(); } else if ($.hasClass(t, 'mFileInfo')) { e.preventDefault(); e.stopPropagation(); } } if (Main.hasMobileLayout && (Config.disableAll || !Config.imageExpansion)) { if (t.parentNode && t.parentNode.hasAttribute('data-m')) { ImageExpansion.setMobileSrc(t.parentNode); } } }; Main.onThreadMouseOver = function(e) { var t = e.target; if (Config.quotePreview && $.hasClass(t, 'quotelink') && !$.hasClass(t, 'deadlink') && !$.hasClass(t, 'linkfade')) { QuotePreview.resolve(e.target); } else if (Config.imageHover && ( (t.hasAttribute('data-md5') && !$.hasClass(t.parentNode, 'deleted')) || (t.href && !$.hasClass(t.parentNode, 'fileText') && /(i\.4cdn|is\.4chan)\.org\/[a-z0-9]+\/[0-9]+\.(gif|jpg|png|webm)$/.test(t.href)) ) ) { ImageHover.show(t); } else if ($.hasClass(t, 'dateTime')) { Parser.onDateMouseOver(t); } else if ($.hasClass(t, 'hand')) { Parser.onUIDMouseOver(t); } else if (Config.embedYouTube && t.getAttribute('data-type') === 'yt' && !Main.hasMobileLayout) { Media.showYTPreview(t); } else if (Config.filter && t.hasAttribute('data-filtered')) { QuotePreview.show(t, t.href ? t.parentNode.parentNode.parentNode : t.parentNode.parentNode); } }; Main.onThreadMouseOut = function(e) { var t = e.target; if (Config.quotePreview && $.hasClass(t, 'quotelink')) { QuotePreview.remove(t); } else if (Config.imageHover && (t.hasAttribute('data-md5') || (t.href && !$.hasClass(t.parentNode, 'fileText') && /(i\.4cdn|is\.4chan)\.org\/[a-z0-9]+\/[0-9]+\.(gif|jpg|png|webm)$/.test(t.href)) ) ) { ImageHover.hide(); } else if ($.hasClass(t, 'dateTime') || $.hasClass(t, 'hand')) { Parser.onTipMouseOut(t); } else if (Config.embedYouTube && t.getAttribute('data-type') === 'yt' && !Main.hasMobileLayout) { Media.removeYTPreview(); } else if (Config.filter && t.hasAttribute('data-filtered')) { QuotePreview.remove(t); } }; Main.linkToThread = function(tid, board, post) { return '//' + location.host + '/' + (board || Main.board) + '/thread/' + tid + (post > 0 ? ('#p' + post) : ''); }; Main.addCSS = function() { var style, css = '\ body.hasDropDownNav {\ margin-top: 45px;\ }\ .extButton.threadHideButton {\ float: left;\ margin-right: 5px;\ margin-top: -1px;\ }\ .extButton.replyHideButton {\ margin-top: 1px;\ }\ div.op > span .postHideButtonCollapsed {\ margin-right: 1px;\ }\ .dropDownNav #boardNavMobile, {\ display: block !important;\ }\ .extPanel {\ border: 1px solid rgba(0, 0, 0, 0.20);\ }\ .tomorrow .extPanel {\ border: 1px solid #111;\ }\ .extButton,\ img.pointer {\ width: 18px;\ height: 18px;\ }\ .extControls {\ display: inline;\ margin-left: 5px;\ }\ .extButton {\ cursor: pointer;\ margin-bottom: -4px;\ }\ .trashIcon {\ width: 16px;\ height: 16px;\ margin-bottom: -2px;\ margin-left: 5px;\ }\ .threadUpdateStatus {\ margin-left: 0.5ex;\ }\ .futaba_new .stub,\ .burichan_new .stub {\ line-height: 1;\ padding-bottom: 1px;\ }\ .stub .extControls,\ .stub .wbtn,\ .stub input {\ display: none;\ }\ .stub .threadHideButton {\ float: none;\ margin-right: 2px;\ }\ .right {\ float: right;\ }\ .center {\ display: block;\ margin: auto;\ }\ .pointer {\ cursor: pointer;\ }\ .drag {\ cursor: move !important;\ user-select: none !important;\ -moz-user-select: none !important;\ -webkit-user-select: none !important;\ }\ #quickReply {\ display: block;\ position: fixed;\ padding: 2px;\ font-size: 10pt;\ }\ #qrepHeader,\ #qrHeader {\ font-size: 10pt;\ text-align: center;\ margin-bottom: 1px;\ padding: 0;\ height: 18px;\ line-height: 18px;\ }\ #qrHeader .left { float: left; margin-left: 3px; }\ #qrepClose,\ #qrClose {\ float: right;\ }\ #qrCaptchaContainer { width: 300px; background-color: #eee; overflow: hidden; margin-bottom: 3px }\ .tomorrow #qrCaptchaContainer.t-qr-root { background-color: #323232; }\ .tomorrow #qrCaptchaContainer #t-cnt { filter: invert(85%); }\ #qrForm > div {\ clear: both;\ }\ #quickReply input[type="text"],\ #quickReply textarea,\ #quickReply #recaptcha_response_field {\ border: 1px solid #aaa;\ font-family: arial,helvetica,sans-serif;\ font-size: 10pt;\ outline: medium none;\ width: 296px;\ padding: 2px;\ margin: 0 0 1px 0;\ }\ .tomorrow #quickReply input[type="text"],\ .tomorrow #quickReply textarea,\ .tomorrow #quickReply #recaptcha_response_field {\ border: 1px solid #515151;\ background-color: #282a2e;\ color: #c5c8c6;\ }\ .tomorrow #quickReply input[type="text"]:focus,\ .tomorrow #quickReply textarea:focus {\ border: 1px solid #757575;\ }\ #quickReply textarea {\ min-width: 296px;\ float: left;\ }\ .tomorrow #quickReply input::placeholder {\ color: 919191 !important;\ }\ #quickReply input[type="submit"] {\ width: 75px;\ margin: 0;\ float: right;\ }\ #quickReply #qrCapField {\ display: block;\ margin-top: 1px;\ }\ #quickReply input.presubmit {\ margin-right: 1px;\ width: 212px;\ float: left;\ }\ #qrFile {\ width: 130px;\ margin-right: 5px;\ }\ .yotsuba_new #qrFile {\ color:black;\ }\ #qrSpoiler {\ display: inline;\ }\ #qrError {\ width: 292px;\ display: none;\ font-family: monospace;\ background-color: #E62020;\ font-size: 12px;\ color: white;\ padding: 3px 5px;\ text-shadow: 0 1px rgba(0, 0, 0, 0.20);\ clear: both;\ }\ #qrError a:hover,\ #qrError a {\ color: white !important;\ text-decoration: underline;\ }\ #twHeader {\ font-weight: bold;\ text-align: center;\ height: 17px;\ }\ .futaba_new #twHeader,\ .burichan_new #twHeader {\ line-height: 1;\ }\ #twPrune {\ margin-left: 3px;\ margin-top: -1px;\ }\ #twClose {\ float: left;\ margin-top: -1px;\ }\ #threadWatcher {\ max-width: 265px;\ display: block;\ position: absolute;\ padding: 3px;\ }\ #watchList {\ margin: 0;\ padding: 0;\ user-select: none;\ -moz-user-select: none;\ -webkit-user-select: none;\ }\ #watchList li:first-child {\ margin-top: 3px;\ padding-top: 2px;\ border-top: 1px solid rgba(0, 0, 0, 0.20);\ }\ .photon #watchList li:first-child {\ border-top: 1px solid #ccc;\ }\ .yotsuba_new #watchList li:first-child {\ border-top: 1px solid #d9bfb7;\ }\ .yotsuba_b_new #watchList li:first-child {\ border-top: 1px solid #b7c5d9;\ }\ .tomorrow #watchList li:first-child {\ border-top: 1px solid #111;\ }\ #watchList a {\ text-decoration: none;\ }\ #watchList li {\ overflow: hidden;\ white-space: nowrap;\ text-overflow: ellipsis;\ }\ div.post div.image-expanded {\ display: table;\ }\ div.op div.file .image-expanded-anti {\ margin-left: -3px;\ }\ #quote-preview {\ display: block;\ position: absolute;\ top: 0;\ padding: 3px 6px 6px 3px;\ margin: 0;\ }\ #quote-preview .dateTime {\ white-space: nowrap;\ }\ #quote-preview.reveal-spoilers s {\ background-color: #aaa !important;\ color: inherit !important;\ text-decoration: none !important;\ }\ #quote-preview.reveal-spoilers s a {\ background: transparent !important;\ text-decoration: underline;\ }\ .yotsuba_b_new #quote-preview.reveal-spoilers s a,\ .burichan_new #quote-preview.reveal-spoilers s a {\ color: #D00 !important;\ }\ .yotsuba_new #quote-preview.reveal-spoilers s a,\ .futaba_new #quote-preview.reveal-spoilers s a {\ color: #000080 !important;\ }\ .tomorrow #quote-preview.reveal-spoilers s { color: #000 !important; }\ .tomorrow #quote-preview.reveal-spoilers s a { color: #5F89AC !important; }\ .photon #quote-preview.reveal-spoilers s a {\ color: #FF6600 !important;\ }\ .yotsuba_new #quote-preview.highlight,\ .yotsuba_b_new #quote-preview.highlight {\ border-width: 1px 2px 2px 1px !important;\ border-style: solid !important;\ }\ .yotsuba_new #quote-preview.highlight {\ border-color: #D99F91 !important;\ }\ .yotsuba_b_new #quote-preview.highlight {\ border-color: #BA9DBF !important;\ }\ .yotsuba_b_new .highlight-anti,\ .burichan_new .highlight-anti {\ border-width: 1px !important;\ background-color: #bfa6ba !important;\ }\ .yotsuba_new .highlight-anti,\ .futaba_new .highlight-anti {\ background-color: #e8a690 !important;\ }\ .tomorrow .highlight-anti {\ background-color: #111 !important;\ border-color: #111;\ }\ .photon .highlight-anti {\ background-color: #bbb !important;\ }\ .op.inlined {\ display: block;\ }\ #quote-preview .inlined,\ #quote-preview .postMenuBtn,\ #quote-preview .extButton,\ #quote-preview .extControls {\ display: none;\ }\ .hasNewReplies { font-weight: bold; }\ .hasYouReplies { font-style: italic; }\ .archivelink {\ opacity: 0.5;\ }\ .deadlink {\ text-decoration: line-through !important;\ }\ div.backlink {\ font-size: 0.8em !important;\ display: inline;\ padding: 0;\ padding-left: 5px;\ }\ .backlink.mobile {\ padding: 3px 5px;\ display: block;\ clear: both !important;\ line-height: 2;\ }\ .op .backlink.mobile,\ #quote-preview .backlink.mobile {\ display: none !important;\ }\ .backlink.mobile .quoteLink {\ padding-right: 2px;\ }\ .backlink span {\ padding: 0;\ }\ .burichan_new .backlink a,\ .yotsuba_b_new .backlink a {\ color: #34345C !important;\ }\ .burichan_new .backlink a:hover,\ .yotsuba_b_new .backlink a:hover {\ color: #dd0000 !important;\ }\ .expbtn {\ margin-right: 3px;\ margin-left: 2px;\ }\ .tCollapsed .rExpanded {\ display: none;\ }\ #stickyNav {\ position: fixed;\ font-size: 0;\ }\ #stickyNav img {\ vertical-align: middle;\ }\ .tu-error {\ color: red;\ }\ .topPageNav {\ position: absolute;\ }\ .yotsuba_b_new .topPageNav {\ border-top: 1px solid rgba(255, 255, 255, 0.25);\ border-left: 1px solid rgba(255, 255, 255, 0.25);\ }\ .newPostsMarker:not(#quote-preview) {\ box-shadow: 0 3px red;\ }\ #toggleMsgBtn {\ float: left;\ margin-bottom: 6px;\ }\ .panelHeader {\ font-weight: bold;\ font-size: 16px;\ text-align: center;\ margin-bottom: 5px;\ margin-top: 5px;\ padding-bottom: 5px;\ border-bottom: 1px solid rgba(0, 0, 0, 0.20);\ }\ .yotsuba_new .panelHeader {\ border-bottom: 1px solid #d9bfb7;\ }\ .yotsuba_b_new .panelHeader {\ border-bottom: 1px solid #b7c5d9;\ }\ .tomorrow .panelHeader {\ border-bottom: 1px solid #111;\ }\ .panelHeader .panelCtrl {\ position: absolute;\ right: 5px;\ top: 5px;\ }\ .UIMenu,\ .UIPanel {\ position: fixed;\ width: 100%;\ height: 100%;\ top: 0;\ left: 0;\ z-index: 100002;\ }\ .UIPanel {\ line-height: 14px;\ font-size: 14px;\ background-color: rgba(0, 0, 0, 0.25);\ }\ .UIPanel:after {\ display: inline-block;\ height: 100%;\ vertical-align: middle;\ content: "";\ }\ .UIPanel > div {\ -moz-box-sizing: border-box;\ box-sizing: border-box;\ display: inline-block;\ height: auto;\ max-height: 100%;\ position: relative;\ width: 400px;\ left: 50%;\ margin-left: -200px;\ overflow: auto;\ box-shadow: 0 0 5px rgba(0, 0, 0, 0.25);\ vertical-align: middle;\ }\ #settingsMenu > div {\ top: 25px;;\ vertical-align: top;\ max-height: 85%;\ }\ .extPanel input[type="text"],\ .extPanel textarea {\ border: 1px solid #AAA;\ outline: none;\ }\ .UIPanel .center {\ margin-bottom: 5px;\ }\ .UIPanel button {\ display: inline-block;\ margin-right: 5px;\ }\ .UIPanel code {\ background-color: #eee;\ color: #000000;\ padding: 1px 4px;\ font-size: 12px;\ }\ .UIPanel ul {\ list-style: none;\ padding: 0;\ margin: 0 0 10px;\ }\ .UIPanel .export-field {\ width: 385px;\ }\ #settingsMenu label input {\ margin-right: 5px;\ }\ .tomorrow #settingsMenu ul {\ border-bottom: 1px solid #282a2e;\ }\ .settings-off {\ padding-left: 3px;\ }\ .settings-cat-lbl {\ font-weight: bold;\ margin: 10px 0 5px;\ padding-left: 5px;\ }\ .settings-cat-lbl img {\ vertical-align: text-bottom;\ margin-right: 5px;\ cursor: pointer;\ width: 18px;\ height: 18px;\ }\ .settings-tip {\ font-size: 0.85em;\ margin: 2px 0 5px 0;\ padding-left: 23px;\ }\ #settings-exp-all {\ padding-left: 7px;\ text-align: center;\ }\ #settingsMenu .settings-cat {\ display: none;\ margin-left: 3px;\ }\ #tex-preview-cnt .extPanel { width: 600px; margin-left: -300px; }\ #tex-preview-cnt textarea,\ #customCSSMenu textarea {\ display: block;\ max-width: 100%;\ min-width: 100%;\ -moz-box-sizing: border-box;\ box-sizing: border-box;\ height: 200px;\ margin: 0 0 5px;\ font-family: monospace;\ }\ #tex-preview-cnt textarea { height: 75px; }\ #output-tex-preview {\ min-height: 75px;\ white-space: pre;\ padding: 0 3px;\ -moz-box-sizing: border-box; box-sizing: border-box;\ }\ #tex-protip { font-size: 11px; margin: 5px 0; text-align: center; }\ a.tex-logo sub { pointer-events: none; }\ #customCSSMenu .right,\ #settingsMenu .right {\ margin-top: 2px;\ }\ #settingsMenu label {\ display: inline-block;\ user-select: none;\ -moz-user-select: none;\ -webkit-user-select: none;\ }\ #filtersHelp > div {\ width: 600px;\ left: 50%;\ margin-left: -300px;\ }\ #filtersHelp h4 {\ font-size: 15px;\ margin: 20px 0 0 10px;\ }\ #filtersHelp h4:before {\ content: "»";\ margin-right: 3px;\ }\ #filtersHelp ul {\ padding: 0;\ margin: 10px;\ }\ #filtersHelp li {\ padding: 3px 0;\ list-style: none;\ }\ #filtersMenu table {\ width: 100%;\ }\ #filtersMenu th {\ font-size: 12px;\ }\ #filtersMenu tbody {\ text-align: center;\ }\ #filtersMenu select,\ #filtersMenu .fPattern,\ #filtersMenu .fBoards,\ #palette-custom-input {\ padding: 1px;\ font-size: 11px;\ }\ #filtersMenu select {\ width: 75px;\ }\ #filtersMenu tfoot td {\ padding-top: 10px;\ }\ #keybindsHelp li {\ padding: 3px 5px;\ }\ .fPattern {\ width: 110px;\ }\ .fBoards {\ width: 25px;\ }\ .fColor {\ width: 60px;\ }\ .fDel {\ font-size: 16px;\ }\ .filter-preview {\ cursor: pointer;\ margin-left: 3px;\ }\ #quote-preview iframe,\ #quote-preview .filter-preview {\ display: none;\ }\ .post-hidden .extButton,\ .post-hidden:not(#quote-preview) .postInfo {\ opacity: 0.5;\ }\ .post-hidden:not(.thread) .postInfo {\ padding-left: 5px;\ }\ .post-hidden:not(#quote-preview) input,\ .post-hidden:not(#quote-preview) .replyContainer,\ .post-hidden:not(#quote-preview) .summary,\ .post-hidden:not(#quote-preview) .op .file,\ .post-hidden:not(#quote-preview) .file,\ .post-hidden .wbtn,\ .post-hidden .postNum span,\ .post-hidden:not(#quote-preview) .backlink,\ div.post-hidden:not(#quote-preview) div.file,\ div.post-hidden:not(#quote-preview) blockquote.postMessage {\ display: none;\ }\ .click-me {\ border-radius: 5px;\ margin-top: 5px;\ padding: 2px 5px;\ position: absolute;\ font-weight: bold;\ z-index: 2;\ white-space: nowrap;\ }\ .yotsuba_new .click-me,\ .futaba_new .click-me {\ color: #800000;\ background-color: #F0E0D6;\ border: 2px solid #D9BFB7;\ }\ .yotsuba_b_new .click-me,\ .burichan_new .click-me {\ color: #000;\ background-color: #D6DAF0;\ border: 2px solid #B7C5D9;\ }\ .tomorrow .click-me {\ color: #C5C8C6;\ background-color: #282A2E;\ border: 2px solid #111;\ }\ .photon .click-me {\ color: #333;\ background-color: #ddd;\ border: 2px solid #ccc;\ }\ .click-me:before {\ content: "";\ border-width: 0 6px 6px;\ border-style: solid;\ left: 50%;\ margin-left: -6px;\ position: absolute;\ width: 0;\ height: 0;\ top: -6px;\ }\ .yotsuba_new .click-me:before,\ .futaba_new .click-me:before {\ border-color: #D9BFB7 transparent;\ }\ .yotsuba_b_new .click-me:before,\ .burichan_new .click-me:before {\ border-color: #B7C5D9 transparent;\ }\ .tomorrow .click-me:before {\ border-color: #111 transparent;\ }\ .photon .click-me:before {\ border-color: #ccc transparent;\ }\ .click-me:after {\ content: "";\ border-width: 0 4px 4px;\ top: -4px;\ display: block;\ left: 50%;\ margin-left: -4px;\ position: absolute;\ width: 0;\ height: 0;\ }\ .yotsuba_new .click-me:after,\ .futaba_new .click-me:after {\ border-color: #F0E0D6 transparent;\ border-style: solid;\ }\ .yotsuba_b_new .click-me:after,\ .burichan_new .click-me:after {\ border-color: #D6DAF0 transparent;\ border-style: solid;\ }\ .tomorrow .click-me:after {\ border-color: #282A2E transparent;\ border-style: solid;\ }\ .photon .click-me:after {\ border-color: #DDD transparent;\ border-style: solid;\ }\ #image-hover {\ position: fixed;\ max-width: 100%;\ max-height: 100%;\ top: 0px;\ right: 0px;\ z-index: 9002;\ }\ .thread-stats {\ float: right;\ margin-right: 5px;\ cursor: default;\ }\ .compact .thread {\ max-width: 75%;\ }\ .dotted {\ text-decoration: none;\ border-bottom: 1px dashed;\ }\ .linkfade {\ opacity: 0.5;\ }\ #quote-preview .linkfade {\ opacity: 1.0;\ }\ kbd {\ background-color: #f7f7f7;\ color: black;\ border: 1px solid #ccc;\ border-radius: 3px 3px 3px 3px;\ box-shadow: 0 1px 0 #ccc, 0 0 0 2px #fff inset;\ font-family: monospace;\ font-size: 11px;\ line-height: 1.4;\ padding: 0 5px;\ }\ .deleted {\ opacity: 0.66;\ }\ div.collapseWebm { text-align: center; margin-top: 10px; }\ .noPictures a.fileThumb img:not(.expanded-thumb) {\ opacity: 0;\ }\ .noPictures.futaba_new a.fileThumb,\ .noPictures.yotsuba_new a.fileThumb {\ border: 1px solid #800;\ }\ .noPictures.burichan_new a.fileThumb,\ .noPictures.yotsuba_b_new a.fileThumb {\ border: 1px solid #34345C;\ }\ .noPictures.tomorrow a.fileThumb:not(.expanded-thumb) {\ border: 1px solid #C5C8C6;\ }\ .noPictures.photon a.fileThumb:not(.expanded-thumb) {\ border: 1px solid #004A99;\ }\ .spinner {\ margin-top: 2px;\ padding: 3px;\ display: table;\ }\ #settings-presets {\ position: relative;\ top: -1px;\ }\ #colorpicker { \ position: fixed;\ text-align: center;\ }\ .colorbox {\ font-size: 10px;\ width: 16px;\ height: 16px;\ line-height: 17px;\ display: inline-block;\ text-align: center;\ background-color: #fff;\ border: 1px solid #aaa;\ text-decoration: none;\ color: #000;\ cursor: pointer;\ vertical-align: top;\ }\ #palette-custom-input {\ vertical-align: top;\ width: 45px;\ margin-right: 2px;\ }\ #qrDummyFile {\ float: left;\ margin-right: 5px;\ width: 220px;\ cursor: default;\ -moz-user-select: none;\ -webkit-user-select: none;\ -ms-user-select: none;\ user-select: none;\ white-space: nowrap;\ text-overflow: ellipsis;\ overflow: hidden;\ }\ #qrDummyFileLabel {\ margin-left: 3px;\ }\ .depageNumber {\ position: absolute;\ right: 5px;\ }\ .depagerEnabled .depagelink {\ font-weight: bold;\ }\ .depagerEnabled strong {\ font-weight: normal;\ }\ .depagelink {\ display: inline-block;\ padding: 4px 0;\ cursor: pointer;\ text-decoration: none;\ }\ .burichan_new .depagelink,\ .futaba_new .depagelink {\ text-decoration: underline;\ }\ #customMenuBox {\ margin: 0 auto 5px auto;\ width: 385px;\ display: block;\ }\ .preview-summary {\ display: block;\ }\ #swf-embed-header {\ padding: 0 0 0 3px;\ font-weight: normal;\ height: 20px;\ line-height: 20px;\ }\ .yotsuba_new #swf-embed-header,\ .yotsuba_b_new #swf-embed-header {\ height: 18px;\ line-height: 18px;\ }\ #swf-embed-close {\ position: absolute;\ right: 0;\ top: 1px;\ }\ #qr-painter-ctrl { text-align: center; }\ #qr-painter-ctrl label { margin-right: 4px; }\ .open-qr-wrap {\ text-align: center;\ width: 200px;\ position: absolute;\ margin-left: 50%;\ left: -100px;\ }\ .postMenuBtn {\ margin-left: 5px;\ text-decoration: none;\ line-height: 1em;\ display: inline-block;\ -webkit-transition: -webkit-transform 0.1s;\ -moz-transition: -moz-transform 0.1s;\ transition: transform 0.1s;\ width: 1em;\ height: 1em;\ text-align: center;\ outline: none;\ opacity: 0.8;\ }\ .postMenuBtn:hover{\ opacity: 1;\ }\ .yotsuba_new .postMenuBtn,\ .futaba_new .postMenuBtn {\ color: #000080;\ }\ .tomorrow .postMenuBtn {\ color: #5F89AC;\ }\ .tomorrow .postMenuBtn:hover {\ color: #81a2be;\ }\ .photon .postMenuBtn {\ color: #FF6600;\ }\ .photon .postMenuBtn:hover {\ color: #FF3300;\ }\ .menuOpen {\ -webkit-transform: rotate(90deg);\ -moz-transform: rotate(90deg);\ -ms-transform: rotate(90deg);\ transform: rotate(90deg);\ }\ .settings-sub label:before {\ border-bottom: 1px solid;\ border-left: 1px solid;\ content: " ";\ display: inline-block;\ height: 8px;\ margin-bottom: 5px;\ width: 8px;\ }\ .settings-sub {\ margin-left: 25px;\ }\ .settings-tip.settings-sub {\ padding-left: 32px;\ }\ .centeredThreads .opContainer {\ display: block;\ }\ .centeredThreads .postContainer {\ margin: auto;\ width: 75%;\ }\ .centeredThreads .sideArrows {\ display: none;\ }\ .centre-exp {\ width: auto !important;\ clear: both;\ }\ .centeredThreads .summary {\ margin-left: 12.5%;\ display: block;\ }\ .centre-exp div.op{\ display: table;\ }\ #yt-preview { position: absolute; }\ #yt-preview img { display: block; }\ .autohide-nav { transition: top 0.2s ease-in-out }\ #feedback {\ position: fixed;\ top: 10px;\ text-align: center;\ width: 100%;\ z-index: 9999;\ }\ .feedback-notify,\ .feedback-error {\ border-radius: 5px;\ cursor: pointer;\ color: #fff;\ padding: 3px 6px;\ font-size: 16px;\ box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);\ text-shadow: 0 1px rgba(0, 0, 0, 0.2);\ }\ .feedback-error { background-color: #C41E3A; }\ .feedback-notify { background-color: #00A550; }\ .boardSelect .custom-menu-ctrl, .boardSelect .customBoardList { margin-left: 10px; }\ .boardSelect .custom-menu-ctrl { display: none; }\ .boardSelect:hover .custom-menu-ctrl { display: inline; }\ .persistentNav .boardList a, .persistentNav .customBoardList a, #boardNavMobile .boardSelect a { text-decoration: none; }\ @media only screen and (max-width: 480px) {\ .thread-stats { float: none; text-align: center; }\ .ts-replies:before { content: "Replies: "; }\ .ts-images:before { content: "Images: "; }\ .ts-ips:before { content: "Posters: "; }\ .ts-page:before { content: "Page: "; }\ #threadWatcher {\ max-width: none;\ padding: 3px 0;\ left: 0;\ width: 100%;\ border-left: none;\ border-right: none;\ }\ #watchList {\ padding: 0 10px;\ }\ div.post div.postInfoM span.nameBlock { clear: none }\ .btn-row {\ margin-top: 5px;\ }\ .image-expanded .mFileName {\ display: block;\ margin-bottom: 2px;\ }\ .mFileName { display: none }\ .mobile-report {\ float: right;\ font-size: 11px;\ margin-bottom: 3px;\ margin-left: 10px;\ }\ .mobile-report:after {\ content: "]";\ }\ .mobile-report:before {\ content: "[";\ }\ .nws .mobile-report:after {\ color: #800000;\ }\ .nws .mobile-report:before {\ color: #800000;\ }\ .ws .mobile-report {\ color: #34345C;\ }\ .nws .mobile-report {\ color:#0000EE;\ }\ .reply .mobile-report {\ margin-right:5px;\ }\ .postLink .mobileHideButton {\ margin-right: 3px;\ }\ .board .mobile-hr-hidden {\ margin-top: 10px !important;\ }\ .board > .mobileHideButton {\ margin-top: -20px !important;\ }\ .board > .mobileHideButton:first-child {\ margin-top: 10px !important;\ }\ .extButton.threadHideButton {\ float: none;\ margin: 0;\ margin-bottom: 5px;\ }\ .mobile-post-hidden {\ display: none;\ }\ #toggleMsgBtn {\ display: none;\ }\ .mobile-tu-status {\ height: 20px;\ line-height: 20px;\ }\ .mobile-tu-show {\ width: 150px;\ margin: auto;\ display: block;\ text-align: center;\ }\ .button input {\ margin: 0 3px 0 0;\ position: relative;\ top: -2px;\ border-radius: 0;\ height: 10px;\ width: 10px;\ }\ .UIPanel > div {\ width: 320px;\ margin-left: -160px;\ }\ .UIPanel .export-field {\ width: 300px;\ }\ .yotsuba_new #quote-preview.highlight,\ #quote-preview {\ border-width: 1px !important;\ }\ .yotsuba_new #quote-preview.highlight {\ border-color: #D9BFB7 !important;\ }\ #quickReply input[type="text"],\ #quickReply textarea,\ .extPanel input[type="text"],\ .extPanel textarea {\ font-size: 16px;\ }\ #quickReply {\ position: absolute;\ left: 50%;\ margin-left: -154px;\ }\ .m-dark .button {\ background-color: rgb(27,28,30);\ background-image: url("//s.4cdn.org/image/buttonfade-dark.png");\ background-repeat: repeat-x;\ border: 1px solid #282A2E;\ }\ .depaged-ad { margin-top: -25px; margin-bottom: -25px; }\ .depageNumber { font-size: 10px; margin-top: -21px; }\ .m-dark a, .m-dark div#absbot a { color: #81A2BE !important; }\ .m-dark a:hover { color: #5F89AC !important; }\ .m-dark .button a, .m-dark .button:hover, .m-dark .button { color: #707070 !important; }\ .m-dark #boardNavMobile { background-color: #1D1F21; border-bottom: 2px solid #282A2E; }\ body.m-dark { background: #1D1F21 none; color: #C5C8C6; }\ .m-dark #globalToggle {\ background-color: #FFADAD;\ background-image: url("//s.4cdn.org/image/buttonfade-red.png");\ border: 1px solid #C45858;\ color: #880000 !important;\ }\ .m-dark .boardTitle { color: #C5C8C6; }\ .m-dark .mobile-report { color: #81a2be !important; }\ .m-dark .mobile-report:after,\ .m-dark .mobile-report:before { color: #1d1f21 !important; }\ .m-dark hr, .m-dark div.board > hr { border-top: 1px solid #282A2E; }\ .m-dark div.opContainer,\ .m-dark div.reply { background-color: #282A2E; }\ .m-dark .preview { background-color: #282A2E; border: 1px solid #333 !important; }\ .m-dark div.post div.postInfoM { background-color: #212326; border-bottom: 1px solid #2D2F33; }\ .m-dark div.postLink,\ .m-dark .backlink.mobile { background-color: #212326; border-top: 1px solid #2D2F33; }\ .m-dark div.post div.postInfoM span.dateTime,\ .m-dark div.postLink span.info,\ .m-dark div.post div.postInfoM span.dateTime a { color: #707070 !important; }\ .m-dark span.subject { color: #B294BB !important; }\ .m-dark .highlightPost:not(.op) { background: #3A171C !important; }\ .m-dark .reply:target, .m-dark .reply.highlight { background: #1D1D21 !important; padding: 2px; }\ .m-dark .reply:target, .m-dark .reply.highlight { background: #1D1D21 !important; padding: 2px; }\ }'; style = document.createElement('style'); style.setAttribute('type', 'text/css'); style.textContent = css; document.head.appendChild(style); }; Main.init();