4chan/js/extension.js
2025-04-17 14:46:47 -05:00

11227 lines
265 KiB
JavaScript

/********************************
* *
* 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(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#039;/g, "'")
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>');
};
Parser.encodeSpecialChars = function(str) {
return str.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
};
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 = '<div class="postLink mobile"><span class="info">'
+ tmp + '</span><a href="'
+ '//boards.' + $L.d(board) + '/' + board + '/thread/' + data.no
+ '" class="button">View Thread</a></div>';
postType = 'op';
replySpan = '&nbsp; <span>[<a href="'
+ '//boards.' + $L.d(board) + '/' + board + '/thread/' + data.no + (data.semantic_url ? ('/' + data.semantic_url) : '')
+ '" class="replylink" rel="canonical">Reply</a>]</span>';
}
if (board != Main.board) {
boardTag = '<span class="boardBlock">/' + board + '/</span> ';
}
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 = ' <span class="posteruid id_'
+ data.id + '">(ID: <span class="hand" title="Highlight posts by this ID">'
+ data.id + '</span>)</span> ';
}
else {
userId = '';
}
switch (data.capcode) {
case 'admin_highlight':
highlight = ' highlightPost';
/* falls through */
case 'admin':
capcodeStart = ' <strong class="capcode hand id_admin" '
+ 'title="Highlight posts by Administrators">## Admin</strong>';
capcodeClass = ' capcodeAdmin';
capcode = ' <img src="' + Parser.icons.admin + '" '
+ 'alt="This user is a 4chan Administrator." '
+ 'title="This user is a 4chan Administrator." class="identityIcon">';
break;
case 'mod':
capcodeStart = ' <strong class="capcode hand id_mod" '
+ 'title="Highlight posts by Moderators">## Mod</strong>';
capcodeClass = ' capcodeMod';
capcode = ' <img src="' + Parser.icons.mod + '" '
+ 'alt="This user is a 4chan Moderator." '
+ 'title="This user is a 4chan Moderator." class="identityIcon">';
break;
case 'developer':
capcodeStart = ' <strong class="capcode hand id_developer" '
+ 'title="Highlight posts by Developers">## Developer</strong>';
capcodeClass = ' capcodeDeveloper';
capcode = ' <img src="' + Parser.icons.dev + '" '
+ 'alt="This user is a 4chan Developer." '
+ 'title="This user is a 4chan Developer." class="identityIcon">';
break;
case 'manager':
capcodeStart = ' <strong class="capcode hand id_manager" '
+ 'title="Highlight posts by Managers">## Manager</strong>';
capcodeClass = ' capcodeManager';
capcode = ' <img src="' + Parser.icons.manager + '" '
+ 'alt="This user is a 4chan Manager." '
+ 'title="This user is a 4chan Manager." class="identityIcon">';
break;
case 'founder':
capcodeStart = ' <strong class="capcode hand id_admin" '
+ 'title="Highlight posts by the Founder">## Founder</strong>';
capcodeClass = ' capcodeAdmin';
capcode = ' <img src="' + Parser.icons.founder + '" '
+ 'alt="This user is 4chan\'s Founder." '
+ 'title="This user is 4chan\'s Founder." class="identityIcon">';
break;
case 'verified':
capcodeStart = ' <strong class="capcode hand id_verified" '
+ 'title="Highlight posts by Verified Users">## Verified</strong>';
capcodeClass = ' capcodeVerified';
capcode = '';
break;
}
if (data.email) {
emailStart = '<a href="mailto:' + data.email.replace(/ /g, '%20') + '" class="useremail">';
emailEnd = '</a>';
}
if (data.flag_name) {
flag = ' <span title="' + data.flag_name + '" class="bfl bfl-'
+ data.board_flag.toLowerCase() + '"></span>';
}
else if (data.country_name) {
flag = ' <span title="' + data.country_name + '" class="flag flag-'
+ data.country.toLowerCase() + '"></span>';
}
else {
flag = '';
}
if (data.filedeleted) {
fileHtml = '<div id="f' + data.no + '" class="file"><span class="fileThumb"><img src="'
+ Parser.icons.del + '" class="fileDeletedRes" alt="File deleted."></span></div>';
}
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 = '<a class="fileThumb' + fileClass + '" href="' + filePath
+ '" target="_blank"' + (data.m_img ? ' data-m' : '') + '><img src="' + fileThumb
+ '" alt="' + size + 'B" data-md5="' + data.md5
+ '" style="height: ' + data.tn_h + 'px; width: '
+ data.tn_w + 'px;">'
+ '<div data-tip data-tip-cb="mShowFull" class="mFileInfo mobile">'
+ size + 'B ' + data.ext.slice(1).toUpperCase()
+ '</div></a>';
fileInfo = '<div class="fileText" id="fT' + data.no + fileSpoilerTip
+ '>File: <a' + (needFileTip ? (' title="' + longFile + '"') : '')
+ ' href="' + filePath + '" target="_blank">'
+ fileName + '</a> (' + size + 'B, ' + fileDims + ')</div>';
}
else {
filePath = imgDir + '/' + data.filename + data.ext;
fileDims += ', ' + data.tag;
fileInfo = '<div class="fileText" id="fT' + data.no + '"'
+ '>File: <a href="' + filePath + '" target="_blank">'
+ data.filename + '.swf</a> (' + size + 'B, ' + fileDims + ')</div>';
}
fileHtml = '<div id="f' + data.no + '" class="file">'
+ fileInfo + imgSrc + '</div>';
}
if (data.trip) {
tripcode = ' <span class="postertrip">' + data.trip + '</span>';
}
name = data.name || '';
if (Main.hasMobileLayout && name.length > 30) {
mName = '<span class="name" data-tip data-tip-cb="mShowFull">'
+ Parser.truncate(name, 30) + '(...)</span> ';
}
else {
mName = '<span class="name">' + name + '</span> ';
}
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 = '<span class="summary preview-summary">' + postCountStr + '.</span>';
}
if (data.sticky) {
threadIcons += '<img class="stickyIcon retina" title="Sticky" alt="Sticky" src="'
+ Main.icons2.sticky + '"> ';
}
if (data.closed) {
if (data.archived) {
threadIcons += '<img class="archivedIcon retina" title="Archived" alt="Archived" src="'
+ Main.icons2.archived + '"> ';
}
else {
threadIcons += '<img class="closedIcon retina" title="Closed" alt="Closed" src="'
+ Main.icons2.closed + '"> ';
}
}
if (data.sub === undefined) {
subject = '<span class="subject"></span> ';
}
else if (Main.hasMobileLayout && data.sub.length > 30) {
subject = '<span class="subject" data-tip data-tip-cb="mShowFull">'
+ Parser.truncate(data.sub, 30) + '(...)</span> ';
}
else {
subject = '<span class="subject">' + data.sub + '</span> ';
}
}
else {
subject = '';
}
container.className = 'postContainer ' + postType + 'Container';
container.id = 'pc' + data.no;
if (data.xa24) {
container.className += ' p-xa24-' + data.xa24;
}
container.innerHTML =
(isOP ? '' : '<div class="sideArrows" id="sa' + data.no + '">&gt;&gt;</div>') +
'<div id="p' + data.no + '" class="post ' + postType + highlight + '">' +
'<div class="postInfoM mobile" id="pim' + data.no + '">' +
'<span class="nameBlock' + capcodeClass + '">' + boardTag +
mName + tripcode +
capcodeStart + capcode + userId + flag +
'<br>' + subject +
'</span><span class="dateTime postNum" data-utc="' + data.time + '">' +
data.now + ' <a href="' + noLink + '" title="Link to this post">No.</a><a href="' +
quoteLink + '" title="Reply to this post">' +
data.no + '</a></span>' +
'</div>' +
(isOP ? fileHtml : '') +
'<div class="postInfo desktop" id="pi' + data.no + '"' +
(board != Main.board ? (' data-board="' + board + '"') : '') + '>' +
'<input type="checkbox" name="' + data.no + '" value="delete"> ' + boardTag +
subject +
'<span class="nameBlock' + capcodeClass + '">' + emailStart +
'<span class="name">' + name + '</span>'
+ tripcode
+ (data.since4pass ? (' <span title="Pass user since ' + data.since4pass + '" class="n-pu"></span>') : '')
+ (data.xa24 ? (' <span data-tip="' + data.xa24 + '" class="n-xa24 n-xa24-' + data.xa24 + '"></span>') : '')
+ capcodeStart + emailEnd + capcode + userId + flag +
' </span> ' +
'<span class="dateTime" data-utc="' + data.time + '">' + data.now + '</span> ' +
'<span class="postNum desktop">' +
'<a href="' + noLink + '" title="Link to this post">No.</a><a href="' +
quoteLink + '" title="Reply to this post">' + data.no + '</a>'
+ threadIcons + replySpan +
'</span>' +
'</div>' +
(isOP ? '' : fileHtml) +
'<blockquote class="postMessage" id="m' + data.no + '">'
+ (data.com || '') + capcode_replies + summary + '</blockquote> ' +
'</div>' + 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('&#44;', ',');
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 = '&gt;&gt;&gt;/' + board + '/';
}
else {
prelink = '';
pretext = '&gt;&gt;';
}
html = '<br><br><span class="capcodeReplies"><span class="smaller">';
for (capcode in replies) {
html += '<span class="bold">' + map[capcode] + ' Replies:</span> ';
post_ids = replies[capcode];
for (i = 0; id = post_ids[i]; ++i) {
html += '<a class="quotelink" href="'
+ prelink + tid + '#p' + id + '">' + pretext + id + '</a> ';
}
}
return html + '</span></span>';
};
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 = '<img alt="H" class="extButton threadHideButton"'
+ 'data-cmd="hide" data-id="' + tid + '" src="'
+ Main.icons.minus + '" title="Hide thread">';
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 = ' <a href="#" data-pid="' + pid + '" data-tid="'
+ tid + '" data-tip="Open in Tegaki" data-cmd="qr-painter-edit">Edit</a>';
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 = '<a href="' + href + '" class="quotelink">&gt;&gt;' + pid + '</a> ';
}
else {
bl.innerHTML = '<a href="' + href + '" class="quotelink">&gt;&gt;' + pid
+ '</a><a href="' + href + '" class="quoteLink"> #</a> ';
}
// 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. <a href="thread/'
+ tid + '" class="replylink">Click here</a> 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 = '<svg width="28" height="28" fill="currentColor" viewBox="0 0 16 16"><path d="M5.5 3.5A1.5 1.5 0 0 1 7 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5zm5 0A1.5 1.5 0 0 1 12 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5z"/></svg><svg width="28" height="28" fill="currentColor" viewBox="0 0 16 16"><path d="m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z"/></svg>';
$.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 = '<svg width="28" height="28" fill="currentColor" viewBox="0 0 16 16"><path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"/><path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"/><path d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707zM6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06z"/></svg><svg width="28" height="28" fill="currentColor" viewBox="0 0 16 16"><path d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zm7.137 2.096a.5.5 0 0 1 0 .708L12.207 8l1.647 1.646a.5.5 0 0 1-.708.708L11.5 8.707l-1.646 1.647a.5.5 0 0 1-.708-.708L10.793 8 9.146 6.354a.5.5 0 1 1 .708-.708L11.5 7.293l1.646-1.647a.5.5 0 0 1 .708 0z"/></svg>';
$.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 = '<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M5.828 10.172a.5.5 0 0 0-.707 0l-4.096 4.096V11.5a.5.5 0 0 0-1 0v3.975a.5.5 0 0 0 .5.5H4.5a.5.5 0 0 0 0-1H1.732l4.096-4.096a.5.5 0 0 0 0-.707zm4.344 0a.5.5 0 0 1 .707 0l4.096 4.096V11.5a.5.5 0 1 1 1 0v3.975a.5.5 0 0 1-.5.5H11.5a.5.5 0 0 1 0-1h2.768l-4.096-4.096a.5.5 0 0 1 0-.707zm0-4.344a.5.5 0 0 0 .707 0l4.096-4.096V4.5a.5.5 0 1 0 1 0V.525a.5.5 0 0 0-.5-.5H11.5a.5.5 0 0 0 0 1h2.768l-4.096 4.096a.5.5 0 0 0 0 .707zm-4.344 0a.5.5 0 0 1-.707 0L1.025 1.732V4.5a.5.5 0 0 1-1 0V.525a.5.5 0 0 1 .5-.5H4.5a.5.5 0 0 1 0 1H1.732l4.096 4.096a.5.5 0 0 1 0 .707z"/></svg><svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M5.5 0a.5.5 0 0 1 .5.5v4A1.5 1.5 0 0 1 4.5 6h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5zm5 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 10 4.5v-4a.5.5 0 0 1 .5-.5zM0 10.5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 6 11.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zm10 1a1.5 1.5 0 0 1 1.5-1.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4z"/></svg>';
$.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 = '<ul><li data-cmd="report" data-id="' + pid
+ (board ? ('" data-board="' + board + '"') : '"')
+ '">Report post</li>';
if (isOP) {
if (Config.threadHiding && !Main.tid) {
html += '<li data-cmd="hide" data-id="' + pid + '">'
+ ($.hasClass($.id('t' + pid), 'post-hidden') ? 'Unhide' : 'Hide')
+ ' thread</li>';
}
if (Config.threadWatcher) {
html += '<li data-cmd="watch" data-id="' + pid + '">'
+ (ThreadWatcher.watched[pid + '-' + Main.board] ? 'Remove from' : 'Add to')
+ ' watch list</li>';
}
}
else if (el = $.id('pc' + pid)) {
html += '<li data-cmd="hide-r" data-id="' + pid + '">'
+ ($.hasClass(el, 'post-hidden') ? 'Unhide' : 'Hide')
+ ' post</li>';
}
if (Main.hasMobileLayout) {
html += '<li><a href="#" data-id="' + pid
+ '" data-cmd="del-post">Delete post</a></li>';
}
if (file = $.id('fT' + pid)) {
el = $.cls('fileThumb', file.parentNode)[0];
if (el) {
if (/(gif|jpg|png)$/.test(el.href)) {
href = el.href;
}
else {
href = 'http://i.4cdn.org/' + Main.board + '/'
+ el.href.match(/\/([0-9]+)m?\..+$/)[1] + 's.jpg';
}
if (Main.hasMobileLayout) {
html += '<li><a href="#" data-id="' + pid
+ '" data-cmd="del-file">Delete file</a></li>'
+ '<li><a href="' + (el.getAttribute('data-orig') || el.href)
+ '" target="_blank">Open original file</a></li>'
+ '<li><a href="https://lens.google.com/uploadbyurl?url=' + href
+ '" target="_blank">Search image on Google</a></li>'
+ '<li><a href="https://www.yandex.com/images/search?img_url=' + href
+ '&amp;rpt=imageview" target="_blank">Search image on Yandex</a></li>'
+ '<li><a href="https://saucenao.com/search.php?url='
+ href + '" target="_blank">Search image on SauceNAO</a></li>';
}
else {
html += '<li><ul>'
+ '<li><a href="https://lens.google.com/uploadbyurl?url=' + href
+ '" target="_blank">Google</a></li>'
+ '<li><a href="https://www.yandex.com/images/search?img_url=' + href
+ '&amp;rpt=imageview" target="_blank">Yandex</a></li>'
+ '<li><a href="https://saucenao.com/search.php?url='
+ href + '" target="_blank">SauceNAO</a></li></ul>Image search &raquo</li>';
}
}
}
if (Config.filter) {
html += '<li><a href="#" data-cmd="filter-sel">Filter selected text</a></li>';
}
div = document.createElement('div');
div.id = 'post-menu';
div.className = 'dd-menu';
div.innerHTML = html + '</ul>';
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 = '<div id="js-sf-status" class="js-sf-' + cls + '">' + msg + '</div>';
}
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 = '[<a title="Toggle infinite scroll" '
+ 'class="depagelink" href="" data-cmd="depage">All</a>]';
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 = '<span class="button">Close</span>';
link.parentNode.appendChild(el);
}
else {
fileText = thumb.parentNode.previousElementSibling;
el = document.createElement('span');
el.className = 'collapseWebm';
el.innerHTML = '-[<a href="#">Close</a>]';
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 = '\
<div class="extPanel reply"><div class="panelHeader"><span class="tex-logo">T<sub>e</sub>X</span> Preview\
<span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="close-tex-preview" src="'
+ Main.icons.cross + '"></span></div><div id="tex-protip">Use [math][/math] tags for inline, and [eqn][/eqn] tags for block equations.</div><textarea id="input-tex-preview"></textarea>\
<div id="output-tex-preview"></div></div>';
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 = '[<a href="#" class="open-qr-link" data-cmd="open-qr">Post a Reply</a>]';
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 =
'<div id="qrHeader" class="drag postblock">'
+ (window.math_tags ? '<a data-cmd="open-tex-preview" class="desktop pointer left tex-logo" '
+ 'data-tip="Preview TeX equations">T<sub>e</sub>X</a>' : '')
+ 'Reply to Thread No.<span id="qrTid">'
+ tid + '</span><img alt="X" src="' + Main.icons.cross + '" id="qrClose" '
+ 'class="extButton" title="Close Window"></div>';
form = postForm.parentNode.cloneNode(false);
form.setAttribute('name', 'qrPost');
form.innerHTML =
(
(mfs = $.byName('MAX_FILE_SIZE')[0])
? ('<input type="hidden" value="' + mfs.value + '" name="MAX_FILE_SIZE">')
: ''
)
+ '<input type="hidden" value="regist" name="mode">'
+ '<input id="qrResto" type="hidden" value="' + tid + '" name="resto">';
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 = '<label>[<input type="checkbox" value="on" name="spoiler">Spoiler?]</label>';
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(/<!-- thread:([0-9]+),no:([0-9]+) -->/)) {
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.'
+ '<br>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 = '<div class="drag" id="twHeader">'
+ (Main.hasMobileLayout ? ('<img id="twClose" class="pointer" src="'
+ Main.icons.cross + '" alt="X">') : '')
+ 'Thread Watcher'
+ (UA.hasCORS ? ('<img id="twPrune" class="pointer right" src="'
+ Main.icons.refresh + '" alt="R" title="Refresh"></div>') : '</div>');
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 += '<li id="watch-' + key
+ '"><span class="pointer" data-cmd="unwatch" data-id="'
+ tuid[0] + '" data-board="' + tuid[1] + '">&times;</span> <a href="'
+ Main.linkToThread(tuid[0], tuid[1]) + '#lr' + this.watched[key][1] + '"';
if (this.watched[key][1] == -1) {
html += ' class="deadlink">';
}
else {
cls = [];
if (this.watched[key][3]) {
cls.push('archivelink');
}
if (this.watched[key][4]) {
cls.push('hasYouReplies');
html += ' title="This thread has replies to your posts"';
}
if (this.watched[key][2]) {
html += ' class="' + (cls[0] ? (cls.join(' ') + ' ') : '')
+ 'hasNewReplies">(' + this.watched[key][2] + ') ';
}
else {
html += (cls[0] ? ('class="' + cls.join(' ') + '"') : '') + '>';
}
}
html += '/' + tuid[1] + '/ - ' + this.watched[key][0] + '</a></li>';
}
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(/(?:<br>)+/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 + '<br><br>';
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 = '<label data-cmd="update">Update</label>';
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
= '<span class="tu-error">' + msg + '</span>';
};
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('<em class="ts-replies" data-tip="Replies (bump limit reached)">' + replies + '</em>');
}
else {
stats.push('<span class="ts-replies" data-tip="Replies">' + replies + '</span>');
}
if (isImageFull) {
stats.push('<em class="ts-images" data-tip="Images (limit reached)">' + images + '</em>');
}
else {
stats.push('<span class="ts-images" data-tip="Images">' + images + '</span>');
}
if (!window.thread_archived) {
if (window.unique_ips) {
stats.push('<span data-tip="Posters" class="ts-ips">' + (ips || window.unique_ips) + '</span>');
}
stats.push('<span data-tip="Page" class="ts-page">' + (this.pageNumber || '?') + '</span>');
}
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(/<br>/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(/<br>/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 = '[<a data-cmd="unfilter" data-filtered="1" href="thread/'
+ tid + '">View</a>]';
}
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 = '\
<div class="extPanel reply"><div class="panelHeader">Filters &amp; Highlights Help\
<span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="filters-help-close" src="'
+ Main.icons.cross + '"></span></div>\
<h4>Tripcode, Name and ID filters:</h4>\
<ul><li>Those use simple string comparison.</li>\
<li>Type them exactly as they appear on 4chan, including the exclamation mark for tripcode filters.</li>\
<li>Example: <code>!Ep8pui8Vw2</code></li></ul>\
<h4>Comment, Subject and E-mail filters:</h4>\
<ul><li><strong>Matching whole words:</strong></li>\
<li><code>feel</code> &mdash; will match <em>"feel"</em> but not <em>"feeling"</em>. This search is case-insensitive.</li></ul>\
<ul><li><strong>AND operator:</strong></li>\
<li><code>feel girlfriend</code> &mdash; will match <em>"feel"</em> AND <em>"girlfriend"</em> in any order.</li></ul>\
<ul><li><strong>Exact match:</strong></li>\
<li><code>"that feel when"</code> &mdash; place double quotes around the pattern to search for an exact string</li></ul>\
<ul><li><strong>Wildcards:</strong></li>\
<li><code>feel*</code> &mdash; matches expressions such as <em>"feel"</em>, <em>"feels"</em>, <em>"feeling"</em>, <em>"feeler"</em>, etc…</li>\
<li><code>idolm*ster</code> &mdash; this can match <em>"idolmaster"</em> or <em>"idolm@ster"</em>, etc…</li></ul>\
<ul><li><strong>Regular expressions:</strong></li>\
<li><code>/feel when no (girl|boy)friend/i</code></li>\
<li><code>/^(?!.*touhou).*$/i</code> &mdash; NOT operator.</li>\
<li><code>/^>/</code> &mdash; comments starting with a quote.</li>\
<li><code>/^$/</code> &mdash; comments with no text.</li></ul>\
<h4>Colors:</h4>\
<ul><li>The color field can accept any valid CSS color:</li>\
<li><code>red</code>, <code>#0f0</code>, <code>#00ff00</code>, <code>rgba( 34, 12, 64, 0.3)</code>, etc…</li></ul>\
<h4>Boards:</h4>\
<ul><li>A space separated list of boards on which the filter will be active. Leave blank to apply to all boards.</li></ul>\
<h4>Auto-watching:</h4>\
<ul><li>Enabling the "Auto" option will automatically add matched threads to the Thread Watcher when it is manually refreshed. This only works when the "Boards" field is not empty, and searches catalog JSON for the selected boards(s).</li></ul>\
<h4>Shortcut:</h4>\
<ul><li>If you have <code>Keyboard shortcuts</code> enabled, pressing <kbd>F</kbd> will add the selected text to your filters.</li></ul>';
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 = '\
<div class="extPanel reply"><div class="panelHeader">Filters &amp; Highlights\
<span class="panelCtrl"><img alt="Help" class="pointer" title="Help" data-cmd="filters-help-open" src="'
+ Main.icons.help
+ '"><img alt="Close" title="Close" class="pointer" data-cmd="filters-close" src="'
+ Main.icons.cross + '"></span></div>\
<table><thead><tr>\
<th></th>\
<th>On</th>\
<th>Pattern</th>\
<th>Boards</th>\
<th>Type</th>\
<th>Color</th>\
<th>Auto</th>\
<th>Hide</th>\
<th>Del</th>\
</tr></thead><tbody id="filter-list"></tbody><tfoot><tr><td colspan="9">\
<button data-cmd="filters-add">Add</button>\
<button class="right" data-cmd="filters-save">Save</button>\
</td></tr></tfoot></table></div>';
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 += '<td><span data-tip="Move Up" data-cmd="filters-up" class="pointer">&uarr;</span></td>';
// On
html += '<td><input type="checkbox"'
+ (filter.active ? ' checked="checked"></td>' : '></td>');
// Pattern
html += '<td><input class="fPattern" type="text" value="'
+ filter.pattern.replace(/"/g, '&quot;') + '"></td>';
// Boards
html += '<td><input class="fBoards" type="text" value="'
+ (filter.boards !== undefined ? filter.boards : '') + '"></td>';
// FIXME
if (filter.type === 3) {
filter.type = 4;
}
// Type
sel = [ '', '', '', '', '', '', '' ];
sel[filter.type] = ' selected="selected"';
html += '<td><select size="1"><option value="0"'
+ sel[0] + '>Tripcode</option><option value="1"'
+ sel[1] + '>Name</option><option value="2"'
+ sel[2] + '>Comment</option><option value="4"'
+ sel[4] + '>ID</option><option value="5"'
+ sel[5] + '>Subject</option><option value="6"'
+ sel[6] + '>Filename</option></select></td>';
// Color
html += '<td><span data-cmd="filters-palette" title="Change Color" class="colorbox fColor" ';
if (!filter.color) {
html += ' data-nocolor="1">&#x2215;';
}
else {
html += ' style="background-color:' + filter.color + '">';
}
html += '</span></td>';
// Auto
html += '<td><input type="checkbox"'
+ (filter.auto ? ' checked="checked"></td>' : '></td>');
// Hide
html += '<td><input type="checkbox"'
+ (filter.hide ? ' checked="checked"></td>' : '></td>');
// Del
html += '<td><span data-cmd="filters-del" class="pointer fDel">&times;</span></td>';
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 = '<div id="colorpicker" class="reply extPanel"><table><tbody>';
for (i = 0; i < rowCount; ++i) {
html += '<tr>';
for (j = 0; j < colCount; ++j) {
html += '<td><div data-cmd="palette-pick" class="colorbox" style="background:'
+ colors[i][j] + '"></div></td>';
}
html += '</tr>';
}
html += '</tbody></table>Custom\
<div id="palette-custom"><input id="palette-custom-input" type="text">\
<div id="palette-custom-ok" data-cmd="palette-pick" title="Select Color" class="colorbox"></div></div>\
[<a href="javascript:;" data-cmd="palette-close">Close</a>]\
[<a href="javascript:;" data-cmd="palette-clear">Clear</a>]</div>';
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 = '&#x2215;';
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 = '[<a href="' + src.href + '">Embed</a>]';
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(/<wbr>/g, '\u200b')
.replace(this.linkRe, this.funk)
.replace(/\u200b/g, '<wbr>');
},
funk: function(match, pfx, url, o, str) {
var m, mm, end, len, sfx;
m = o + match.length;
if (str.slice(m, m + 4) === '</a>') {
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 + '<a href="' + Linkify.derefer
+ encodeURIComponent(url.replace(/\u200b/g, ''))
+ '" target="_blank" class="linkified" rel="noreferrer nofollow">'
+ url + '</a>' + sfx;
}
};
/**
* Media
*/
var Media = {};
Media.init = function() {
this.matchSC = /(?:soundcloud\.com|snd\.sc)\/[^\s<]+(?:<wbr>)?[^\s<]*/g;
this.matchYT = /(?:youtube\.com\/watch\?[^\s]*?v=|youtu\.be\/)[^\s<]+(?:<wbr>)?[^\s<]*(?:<wbr>)?[^\s<]*(?:<wbr>)?[^\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 + '</a>';
}
}
else {
pfx = '<span>' + link + '</span>';
}
return pfx + ' [<a href="javascript:;" data-cmd="embed" data-type="sc">Embed</a>]';
};
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 + '</a>';
}
}
else {
pfx = '<span>' + link + '</span>';
}
return pfx + ' [<a href="javascript:;" data-cmd="embed" data-type="yt">'
+ (!Main.hasMobileLayout ? 'Embed' : 'Open') + '</a>]';
};
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 = '<iframe src="//www.youtube.com/embed/'
+ vid
+ '" width="640" height="360" frameborder="0" allowfullscreen></iframe>';
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 = '\
<div class="extPanel reply"><div class="panelHeader">Custom CSS\
<span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="css-close" src="'
+ Main.icons.cross + '"></span></div>\
<textarea id="customCSSBox"></textarea>\
<div class="center"><button data-cmd="css-save">Save CSS</button></div>\
</td></tr></tfoot></table></div>';
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 = '\
<div class="extPanel reply"><div class="panelHeader">Keyboard Shortcuts\
<span class="panelCtrl"><img data-cmd="keybinds-close" class="pointer" alt="Close" title="Close" src="'
+ Main.icons.cross + '"></span></div>\
<ul>\
<li><strong>Global</strong></li>\
<li><kbd>A</kbd> &mdash; Toggle auto-updater</li>\
<li><kbd>Q</kbd> &mdash; Open Quick Reply</li>\
<li><kbd>R</kbd> &mdash; Update thread</li>\
<li><kbd>W</kbd> &mdash; Watch/Unwatch thread</li>\
<li><kbd>B</kbd> &mdash; Previous page</li>\
<li><kbd>N</kbd> &mdash; Next page</li>\
<li><kbd>I</kbd> &mdash; Return to index</li>\
<li><kbd>C</kbd> &mdash; Open catalog</li>\
<li><kbd>F</kbd> &mdash; Filter selected text</li>\
</ul><ul>\
<li><strong>Quick Reply (always enabled)</strong></li>\
<li><kbd>Ctrl + Click</kbd> the post number &mdash; Quote without linking</li>\
<li><kbd>Ctrl + S</kbd> &mdash; Spoiler tags</li>\
<li><kbd>Esc</kbd> &mdash; Close the Quick Reply</li>\
</ul>';
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 = '[<a data-cmd="custom-menu-edit" title="Edit Menu" href="#">Edit</a>]';
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 = '\
<div class="extPanel reply"><div class="panelHeader">Custom Board List\
<span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-close="1" src="'
+ Main.icons.cross + '"></span></div>\
<input placeholder="Example: jp tg mu" id="customMenuBox" type="text" value="">\
<div class="center"><button data-save="1">Save</button></div></div>';
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 &amp; 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, <em>italics</em> signify bump/image limit has been met', true ]
},
'Filters &amp; Post Hiding': {
filter: [ 'Filter and highlight specific threads/posts [<a href="javascript:;" data-cmd="filters-open">Edit</a>]', 'Enable pattern-based filters' ],
threadHiding: [ 'Thread hiding [<a href="javascript:;" data-cmd="thread-hiding-clear">Clear History</a>]', '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 [<a href="javascript:;" data-cmd="custom-menu-edit">Edit</a>]', '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 [<a href="javascript:;" data-cmd="keybinds-open">Show</a>]', 'Enable handy keyboard shortcuts for common actions' ]
},
'Images &amp; 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 [<a href="javascript:;" data-cmd="css-open">Edit</a>]', '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 = '<div class="extPanel reply"><div class="panelHeader">Settings'
+ '<span class="panelCtrl"><img alt="Close" title="Close" class="pointer" data-cmd="settings-toggle" src="'
+ Main.icons.cross + '"></a>'
+ '</span></div><ul>';
html += '<ul><li id="settings-exp-all">[<a href="#" data-cmd="settings-exp-all">Expand All Settings</a>]</li></ul>';
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 += '<ul><li class="settings-cat-lbl">'
+ '<img alt="" class="settings-expand" src="' + Main.icons.plus + '">'
+ '<span class="settings-expand pointer">'
+ cat + '</span></li><ul class="settings-cat">';
for (key in opts) {
// Mobile layout only?
if (opts[key][4] && !Main.hasMobileLayout) {
continue;
}
html += '<li' + (opts[key][3] ? ' class="settings-sub">' : '>')
+ '<label><input type="checkbox" class="menuOption" data-option="'
+ key + '"' + (Config[key] ? ' checked="checked">' : '>')
+ opts[key][0] + '</label>'
+ (opts[key][1] !== false ? '</li><li class="settings-tip'
+ (opts[key][3] ? ' settings-sub">' : '">') + opts[key][1] : '')
+ '</li>';
}
html += '</ul></ul>';
}
html += '</ul><ul><li class="settings-off">'
+ '<label title="Completely disable the native extension (overrides any checked boxes)">'
+ '<input type="checkbox" class="menuOption" data-option="disableAll"'
+ (Config.disableAll ? ' checked="checked">' : '>')
+ 'Disable the native extension</label></li></ul>'
+ '<div class="center"><button data-cmd="settings-export">Export Settings</button>'
+ '<button data-cmd="settings-save">Save Settings</button></div>';
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 = '\
<div class="extPanel reply"><div class="panelHeader">Export Settings\
<span class="panelCtrl"><img data-cmd="export-close" class="pointer" alt="Close" title="Close" src="'
+ Main.icons.cross + '"></span></div>\
<p class="center">Copy and save the URL below, and visit it from another \
browser or computer to restore your extension and catalog settings.</p>\
<p class="center">\
<input class="export-field" type="text" readonly="readonly" value="' + str + '"></p>\
<p style="margin-top:15px" class="center">Alternatively, you can drag the link below into your \
bookmarks bar and click it to restore.</p>\
<p class="center">[<a target="_blank" href="'
+ str + '">Restore 4chan Settings</a>]</p>';
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 = '<span class="feedback-' + type + '">' + msg + '</span>';
$.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 = '<a href="#bottom">&#9660;</a>'
+ '<a href="javascript:void(0);" id="settingsWindowLinkClassic">Settings</a>'
+ '<a href="//www.' + $L.d(Main.board) + '" target="_top">Home</a></div>';
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 = '<img class="pointer" src="'
+ Main.icons.up + '" data-cmd="totop" alt="▲" title="Top">'
+ '<img class="pointer" src="' + Main.icons.down
+ '" data-cmd="tobottom" alt="▼" title="Bottom">';
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();