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

8967 lines
211 KiB
JavaScript

/********************************
* *
* 4chan Extension *
* *
********************************/
/**
* Helpers
*/
$ = {};
$.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);
};
$.qs = function(sel, root) {
return (root || document).querySelector(sel);
};
$.extend = function(destination, source) {
for (var key in source) {
destination[key] = source[key];
}
};
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;
};
$.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 = {};
Parser.init = function() {
var o, a, h, m, tail, staticPath, tracked, el;
if (Config.filter || Config.embedSoundCloud || Config.embedYouTube || Config.embedVocaroo) {
this.needMsg = true;
}
staticPath = '//s.4cdn.org/image/';
tail = window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif';
this.icons = {
admin: staticPath + 'adminicon' + 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.tid) || {};
}
};
Parser.getTrackedReplies = function(tid) {
var tracked = null;
if (tracked = sessionStorage.getItem('4chan-track-' + Main.board + '-' + tid)) {
tracked = JSON.parse(tracked);
}
return tracked;
};
Parser.saveTrackedReplies = function(tid, replies) {
sessionStorage.setItem(
'4chan-track-' + Main.board + '-' + tid,
JSON.stringify(replies)
);
};
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, 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.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,
subject,
noLink,
quoteLink,
replySpan = '',
noFilename,
decodedFilename,
mobileLink = '',
postType = 'reply',
summary = '',
postCountStr,
resto,
capcode_replies = '',
threadIcons = '',
needFileTip = false,
i, q, href, quotes,
imgDir = '//i.4cdn.org/' + board;
if (data.resto == 0) {
isOP = true;
if (standalone) {
mobileLink = '<div class="postLink mobile"><span class="info"></span><a href="'
+ 'thread/' + data.no + '" class="button">View Thread</a></div>';
postType = 'op';
replySpan = '&nbsp; <span>[<a href="'
+ 'thread/' + data.no + (data.semantic_url ? ('/' + data.semantic_url) : '')
+ '" class="replylink" rel="canonical">Reply</a>]</span>'
}
resto = data.no;
}
else {
resto = data.resto;
}
if (!Main.tid || board != Main.board) {
noLink = 'thread/' + resto + '#p' + data.no;
quoteLink = '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';
case 'admin':
capcodeStart = ' <strong class="capcode hand id_admin"'
+ 'title="Highlight posts by the Administrator">## Admin</strong>';
capcodeClass = ' capcodeAdmin';
capcode = ' <img src="' + Parser.icons.admin + '" '
+ 'alt="This user is the 4chan Administrator." '
+ 'title="This user is the 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;
}
if (data.email) {
emailStart = '<a href="mailto:' + data.email.replace(/ /g, '%20') + '" class="useremail">';
emailEnd = '</a>';
}
if (data.country) {
if (board == 'pol') {
flag = ' <img src="//s.4cdn.org/image/country/troll/'
+ data.country.toLowerCase() + '.gif" alt="'
+ data.country + '" title="' + data.country_name + '" class="countryFlag">';
}
else {
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 = '//0.t.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"><img src="' + fileThumb
+ '" alt="' + size + 'B" data-md5="' + data.md5
+ '" style="height: ' + data.tn_h + 'px; width: '
+ data.tn_w + 'px;">'
+ '<div 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 (isOP) {
if (data.capcode_replies) {
capcode_replies = Parser.buildCapcodeReplies(data.capcode_replies, board, data.no);
}
if (fromQuote && data.replies) {
postCountStr = data.replies + ' post' + (data.replies > 1 ? 's' : '');
if (data.images) {
postCountStr += ' and ' + data.images + ' image repl' +
(data.images > 1 ? 'ies' : 'y');
}
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 + '"> ';
}
}
subject = '<span class="subject">' + (data.sub || '') + '</span> ';
}
else {
subject = '';
}
container.className = 'postContainer ' + postType + 'Container';
container.id = 'pc' + data.no;
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 + '">' +
'<span class="name">' + name + '</span>' + tripcode +
capcodeStart + capcode + userId + flag +
'<br>' + subject +
'</span><span class="dateTime postNum" data-utc="' + data.time + '">' +
data.now + ' <a href="' + data.no + '#p' + data.no + '" title="Link to this post">No.</a>' +
'<a href="javascript:quote(\'' + data.no + '\');" 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"> ' +
subject +
'<span class="nameBlock' + capcodeClass + '">' + emailStart +
'<span class="name">' + name + '</span>' +
tripcode + 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 = '/' + board + '/thread/' + resto + href;
}
}
}
return container;
};
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,
frag;
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('a');
el.href = 'javascript:;';
el.setAttribute('data-cmd', 'hide');
el.setAttribute('data-id', tid);
el.className = 'mobileHideButton button';
el.textContent = 'Hide';
posts[0].nextElementSibling.appendChild(el);
}
else {
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 && (cnt = $.cls('navLinksBot')[0])) {
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';
frag = document.createDocumentFragment();
frag.appendChild(document.createTextNode('['));
frag.appendChild(el.cloneNode(true));
frag.appendChild(document.createTextNode('] '));
cnt.insertBefore(frag, cnt.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.jsMath) {
if (window.jsMath.loaded) {
for (i = j; i < limit; ++i) {
window.jsMath.ProcessBeforeShowing(posts[i]);
}
}
else {
Parser.loadJSMath();
}
}
}
UA.dispatchEvent('4chanParsingDone', { threadId: tid, offset: j, limit: limit });
};
Parser.loadJSMath = function(root) {
if ($.cls('math', root)[0]) {
window.jsMath.Autoload.Script.Push('ProcessBeforeShowing', [ null ]);
window.jsMath.Autoload.LoadJsMath();
}
};
Parser.parseMathOne = function(node) {
if (window.jsMath.loaded) {
window.jsMath.ProcessBeforeShowing(node);
}
else {
Parser.loadJSMath(node);
}
};
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.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(/^(?:\/([^\/]+)\/)?(?: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 = prettyPrintOne(el.innerHTML);
}
}
};
Parser.parsePost = function(pid, tid) {
var hasMobileLayout, cnt, el, pi, href, img, file, msg, filtered, html, filename, txt, finfo, isOP, 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);
}
if (hasMobileLayout) {
if (Config.reportButton) {
el = document.createElement('span');
el.className = 'mobile mobile-report';
el.setAttribute('data-cmd', 'report');
el.setAttribute('data-id', pid);
el.textContent = 'Report';
pi.parentNode.appendChild(el);
}
}
else {
el = document.createElement('a');
el.href = '#';
el.className = 'postMenuBtn';
el.title = 'Post menu';
el.setAttribute('data-cmd', 'post-menu');
el.textContent = '▶';
pi.appendChild(el);
}
if (tid && 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 (Config.backlinks) {
Parser.parseBacklinks(pid, tid);
}
}
if (IDColor.enabled && (uid = $.cls('posteruid', pi.parentNode)[hasMobileLayout ? 0 : 1])) {
IDColor.apply(uid.firstElementChild);
}
if (Config.embedSoundCloud) {
Media.parseSoundCloud(msg);
}
if (Config.embedYouTube) {
Media.parseYouTube(msg);
}
if (Config.embedVocaroo) {
Media.parseVocaroo(msg);
}
if (Config.revealSpoilers
&& (file = document.getElementById('f' + pid))
&& (file = file.children[1])
) {
if ($.hasClass(file, 'imgspoiler')) {
img = file.firstChild;
file.removeChild(img);
img.removeAttribute('style');
isOP = $.hasClass(pi.parentNode, 'op');
img.style.maxWidth = img.style.maxHeight = isOP ? '250px' : '125px';
img.src = '//0.t.4cdn.org'
+ (file.pathname.replace(/([0-9]+).+$/, '/$1s.jpg'));
filename = file.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;
file.insertBefore(img, file.firstElementChild);
}
}
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.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, bid, html, 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) {
if (oRep) {
oRep = oRep + ' post' + (oRep > 1 ? 's' : '');
}
else {
return null;
}
if (oImg) {
oImg = ' and ' + oImg + ' image repl' + (oImg > 1 ? 'ies' : 'y');
}
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;
};
/**
* Sync
*/
var UserSync = {
url: 'https://sys.4chan.org/sync',
timeout: null,
processing: false,
maxDelay: 3600000,
queue: {}
};
UserSync.onEnable = function() {
var tkn = Math.random().toString(16).substring(2)
+ Math.random().toString(16).substring(2);
Main.setCookie('sync', tkn, '4chan.org');
};
UserSync.onDisable = function() {
Main.removeCookie('sync', '4chan.org');
localStorage.removeItem('4chan-sync-ts');
};
UserSync.onSyncNowClick = function() {
UserSync.syncStatus(true);
};
UserSync.purgeSync = function() {
var xhr, tkn;
tkn = Main.getCookie('sync');
if (!tkn) {
alert("Syncing doesn't seem to be enabled on this machine");
return;
}
if (!confirm('All data associated with this sync key will be deleted from the server.')) {
return;
}
xhr = new XMLHttpRequest();
xhr.open('POST', UserSync.url + '?action=purge');
xhr.onload = UserSync.onPurgeSyncLoaded;
xhr.onerror = UserSync.onSyncError;
xhr.withCredentials = true;
xhr.withFeedback = true;
//Feedback.notify('Processing…', false);
xhr.send(JSON.stringify({tkn: tkn}));
};
UserSync.onPurgeSyncLoaded = function() {
var resp = JSON.parse(this.responseText);
if (resp.error) {
return Feedback.error(resp.error);
}
//Feedback.notify('Done');
UserSync.onDisable();
};
UserSync.syncStatus = function(withFeedback) {
var xhr;
if (UserSync.processing) {
console.log('Sync: Already syncing');
return;
}
UserSync.processing = true;
if (withFeedback) {
//Feedback.notify('Syncing…', false);
}
xhr = new XMLHttpRequest();
xhr.open('GET', UserSync.url + '?action=status');
xhr.withCredentials = true;
xhr.withFeedback = withFeedback;
xhr.onerror = UserSync.onSyncError;
xhr.onload = UserSync.onSyncStatusLoaded;
xhr.send(null);
};
UserSync.onSyncStatusLoaded = function() {
var i, key, item, items, get, set, req, xhr, local_ts, remote_ts, data, syncTs, tkn;
UserSync.processing = false;
items = JSON.parse(this.responseText);
if (items.error) {
console.log('Sync: ' + items.error);
return;
}
syncTs = UserSync.getSyncTs();
syncTs.ts = Date.now();
get = [];
set = {};
for (key in items) {
local_ts = syncTs[key] || 0;
remote_ts = items[key] || 0;
if (remote_ts > local_ts) {
get.push(key);
}
else if (local_ts > remote_ts) {
data = localStorage.getItem(key);
if (data) {
set[key] = {
ts: local_ts,
data: JSON.parse(data)
};
}
else {
delete syncTs[key];
}
}
}
UserSync.setSyncTs(syncTs);
req = {};
if (get.length) {
req['get'] = get;
}
for (i in set) {
req['set'] = set;
break;
}
if (!req['get'] && !req['set']) {
if (this.withFeedback) {
//Feedback.notify('Done');
}
if (Config.threadWatcher) {
ThreadWatcher.onUserSyncLoaded();
}
return;
}
tkn = Main.getCookie('sync');
if (!tkn) {
return;
}
req.tkn = tkn;
xhr = new XMLHttpRequest();
xhr.open('POST', UserSync.url + '?action=sync');
xhr.withCredentials = true;
xhr.withFeedback = this.withFeedback;
xhr.onload = UserSync.onSyncLoaded;
xhr.onerror = UserSync.onSyncError;
xhr.send(JSON.stringify(req));
};
UserSync.onSyncError = function() {
var msg = 'Sync: Connection Error';
UserSync.processing = false;
UserSync.resetSyncTs();
console.log(msg);
};
UserSync.getSyncTs = function() {
var data = localStorage.getItem('4chan-sync-ts');
return data ? JSON.parse(data) : {};
};
UserSync.setSyncTs = function(data) {
return localStorage.setItem('4chan-sync-ts', JSON.stringify(data));
};
UserSync.resetSyncTs = function() {
var tsData = UserSync.getSyncTs();
delete tsData.ts;
UserSync.setSyncTs(tsData);
};
UserSync.onSyncLoaded = function() {
var items, key, value, local_ts, syncTs;
items = JSON.parse(this.responseText);
if (items.error) {
console.log('Sync: ' + items.error);
return;
}
if (this.withFeedback) {
//Feedback.notify('Done');
}
syncTs = UserSync.getSyncTs();
syncTs.ts = Date.now();
for (key in items) {
value = items[key];
local_ts = syncTs[key] || 0;
if (+local_ts > +value['ts']) {
continue;
}
localStorage.setItem(key, JSON.stringify(value['data']));
syncTs[key] = value['ts'];
}
UserSync.setSyncTs(syncTs);
if (Config.threadWatcher) {
ThreadWatcher.onUserSyncLoaded();
}
};
UserSync.onQueueProcessed = function() {
var items;
items = JSON.parse(this.responseText);
if (items.error) {
console.log('Sync: ' + items.error);
}
}
UserSync.syncPush = function(key) {
var ts, tsData;
ts = Math.round(Date.now() / 1000);
tsData = UserSync.getSyncTs();
tsData[key] = ts;
UserSync.setSyncTs(tsData);
UserSync.queue[key] = ts;
if (UserSync.timeout) {
clearTimeout(UserSync.timeout);
}
UserSync.timeout = setTimeout(UserSync.syncProcessQueue, 1000);
};
UserSync.syncProcessQueue = function() {
var set, xhr, key, tkn;
tkn = Main.getCookie('sync');
if (!tkn) {
UserSync.queue = {};
return;
}
set = {};
for (key in UserSync.queue) {
set[key] = {
ts: UserSync.queue[key],
data: JSON.parse(localStorage.getItem(key))
}
}
UserSync.queue = {};
xhr = new XMLHttpRequest();
xhr.open('POST', UserSync.url + '?action=sync');
xhr.withCredentials = true;
xhr.onload = UserSync.onQueueProcessed;
xhr.onerror = UserSync.onSyncError;
xhr.send(JSON.stringify({
tkn: tkn,
set: set
}));
};
/**
* Post Menu
*/
var PostMenu = {
activeBtn: null
};
PostMenu.open = function(btn) {
var div, html, pid, board, btnPos, txt, el, href, left, limit, isOP;
PostMenu.close();
pid = btn.parentNode.id.split('pi')[1];
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 (!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 (file = $.id('fT' + pid)) {
el = $.cls('fileThumb', file.parentNode)[0];
if (el) {
if (/\.(png|jpg)$/.test(el.href)) {
href = el.href;
}
else {
href = 'http://0.t.4cdn.org/' + Main.board + '/'
+ el.href.match(/\/([0-9]+)\..+$/)[1] + 's.jpg';
}
html += '<li><ul>'
+ '<li><a href="//www.google.com/searchbyimage?image_url=' + href
+ '" target="_blank">Google</a></li>'
+ '<li><a href="http://iqdb.org/?url='
+ href + '" target="_blank">iqdb</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;
}
};
/**
* 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 (el = $.cls('prev')[0]) {
el.innerHTML = '[<a title="Toggle infinite scroll" '
+ 'class="depagelink" href="" data-cmd="depage">All</a>]';
el = el.firstElementChild;
}
else {
return;
}
if (Config.alwaysDepage) {
this.isEnabled = true;
el.parentNode.parentNode.className += ' depagerEnabled';
Depager.bindHandlers();
if (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
<= (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;
wrap.appendChild(cnt);
window.ados_add_placement(3536, 18130, cnt.id, 4).setZone(zone);
}
else {
wrap = document.createElement('div');
wrap.className = 'bottomad center';
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,
lastReplies, pageId, data, isLastPage, html;
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;
links = $.cls('depagelink');
if (!links.length) {
return;
}
if (type == 'enabled') {
for (i = 0; el = links[i]; ++i) {
el.textContent = 'All';
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) {
el.textContent = 'All';
$.removeClass(el.parentNode.parentNode,'depagerEnabled');
}
}
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 = 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) {
var cnt;
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 t, pfx, src, el, count;
t = link.getAttribute('href').match(/^(?:\/([^\/]+)\/)?(?:thread\/)?([0-9]+)?#p([0-9]+)$/);
if (!t || t[1] == 'rs' || 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]);
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 xhr, 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);
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;
}
cnt.insertBefore(el, link.nextSibling);
}
};
/**
* Quote preview
*/
var QuotePreview = {};
QuotePreview.init = function() {
var thread;
this.regex = /^(?:\/([^\/]+)\/)?(?:thread\/)?([0-9]+)?#p([0-9]+)$/;
this.highlight = null;
this.highlightAnti = null;
this.out = true;
};
QuotePreview.resolve = function(link) {
var self, t, post, ids, offset, pfx;
self = QuotePreview;
self.out = false;
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 xhr, onload, onerror, el, cached, key;
key = board + '-' + tid;
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.out) {
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, postWidth, doc, docWidth, style, pos, quotes, i, j, qid,
top, scrollTop, margin, img;
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';
if (Config.imageExpansion && (img = $.cls('expanded-thumb', post)[0])) {
ImageExpansion.contract(img);
}
}
if (!link.parentNode.className) {
quotes = post.querySelectorAll(
'#' + $.cls('postMessage', post)[0].id + ' > .quotelink'
);
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.out = true;
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);
}
};
/**
* Image expansion
*/
var ImageExpansion = {
activeVideos: [],
timeout: null
};
ImageExpansion.expand = function(thumb) {
var img, el, href, ext;
if (Config.imageHover) {
ImageHover.hide();
}
href = thumb.parentNode.getAttribute('href');
if (ext = href.match(/\.(?:webm|pdf)$/)) {
if (!Main.hasMobileLayout && ext[0] == '.webm') {
return ImageExpansion.expandWebm(thumb);
}
return false;
}
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.expandWebm = function(thumb) {
var el, link, fileText, left, width, href, maxWidth, self;
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;
el = document.createElement('video');
el.muted = true;
el.controls = true;
el.loop = true;
el.autoplay = true;
el.className = 'expandedWebm';
el.onloadedmetadata = ImageExpansion.fitWebm;
el.onplay = ImageExpansion.onWebmPlay;
el.src = href;
link.style.display = 'none';
link.parentNode.appendChild(el);
fileText = thumb.parentNode.previousElementSibling;
el = document.createElement('span');
el.className = 'collapseWebm';
el.innerHTML = '-[<a href="#">Close</a>]';
el.firstElementChild.addEventListener('click', self.collapseWebm, false);
fileText.appendChild(el);
return true;
};
ImageExpansion.fitWebm = function() {
var imgWidth, imgHeight, maxWidth, maxHeight, ratio, left, cntEl,
centerWidth, ofs;
if (Config.centeredThreads) {
centerWidth = $.cls('opContainer')[0].offsetWidth;
cntEl = this.parentNode.parentNode.parentNode;
$.addClass(cntEl, 'centre-exp')
}
left = this.getBoundingClientRect().left;
maxWidth = document.documentElement.clientWidth - left - 25;
maxHeight = document.documentElement.clientHeight;
imgWidth = this.videoWidth;
imgHeight = this.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;
}
this.style.maxWidth = imgWidth + 'px';
this.style.maxHeight = imgHeight + 'px';
if (Config.centeredThreads) {
left = this.getBoundingClientRect().left;
ofs = this.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(e) {
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;
el = cnt.parentNode.parentNode.getElementsByClassName('expandedWebm')[0];
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.onScroll = function(e) {
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;
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;
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');
}
}
};
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;
href = thumb.parentNode.getAttribute('href');
if (ext = href.match(/\.(?:webm|pdf)$/)) {
if (ext[0] == '.webm') {
ImageHover.showWebm(thumb);
}
return;
}
el = document.createElement('img');
el.id = 'image-hover';
el.alt = 'Image';
el.setAttribute('src', href);
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) {
Tip.hide();
}
document.body.removeChild(img);
}
};
ImageHover.showWebm = function(thumb) {
var dims, el, bounds, limit, width;
dims = thumb.parentNode.previousElementSibling.textContent.match(/, ([0-9]+)x[0-9]+/);
width = +dims[1];
el = document.createElement('video');
el.id = 'image-hover';
el.src = thumb.parentNode.getAttribute('href');
el.loop = true;
el.muted = true;
el.autoplay = true;
el.onloadedmetadata = function() { ImageHover.showWebMDuration(this, thumb); };
bounds = thumb.getBoundingClientRect();
limit = window.innerWidth - bounds.right - 20;
if (width > limit) {
el.style.maxWidth = limit + 'px';
}
document.body.appendChild(el);
};
ImageHover.showWebMDuration = function(el, thumb) {
if (!el.parentNode) {
return;
}
var ms = $.prettySeconds(el.duration);
Tip.show(thumb, ms[0] + ':' + ('0' + ms[1]).slice(-2));
};
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 = true;
this.currentTid = null;
this.cooldown = null;
this.timestamp = null;
this.auto = false;
this.btn = null;
this.comField = null;
this.comLength = window.comlen;
this.lenCheckTimeout = null;
this.preuploadSizeLimit = Main.hasMobileLayout ? 0 : 204800;
this.cdElapsed = 0;
this.activeDelay = 0;
this.cooldowns = {};
for (item in window.cooldowns) {
this.cooldowns[item] = window.cooldowns[item] * 1000;
}
this.captchaDelay = 240500;
this.captchaInterval = null;
this.pulse = null;
this.xhr = null;
this.fileDisabled = !!window.imagelimit;
this.tracked = {};
this.lastTid = localStorage.getItem('4chan-cd-' + Main.board + '-tid');
if (Main.tid && !Main.hasMobileLayout && !Main.threadClosed) {
QR.addReplyLink();
}
window.addEventListener('storage', this.syncStorage, false);
};
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]) {
if (key[3] == 'tid') {
QR.lastTid = e.newValue;
}
else {
QR.startCooldown();
}
}
};
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, cd, qrError, cookie;
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">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 =
'<input type="hidden" value="'
+ $.byName('MAX_FILE_SIZE')[0].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';
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';
}
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 += 20;
file.id = 'qrFile';
file.size = '19';
file.addEventListener('change', QR.onFileChange, false);
row.appendChild(file);
if (UA.hasDragAndDrop) {
$.addClass(file, 'qrRealFile');
file = document.createElement('div');
file.id = 'qrDummyFile';
el = document.createElement('button');
el.id = 'qrDummyFileButton';
el.type = 'button';
el.textContent = 'Browse…';
file.appendChild(el);
el = document.createElement('span');
el.id = 'qrDummyFileLabel';
el.textContent = 'No file selected.';
file.appendChild(el);
row.appendChild(file);
}
file.title = 'Shift + Click to remove the file';
}
else {
row.innerHTML = fields[i].children[1].innerHTML;
if (row.firstChild.type == 'hidden') {
el = row.lastChild.previousSibling;
}
else {
el = row.firstChild;
}
if (el.tabIndex > 0) {
el.tabIndex += 20;
}
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';
}
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 = el.querySelector('option[selected]')) {
el2.removeAttribute('selected');
}
if ((cookie = Main.getCookie('4chan_flag')) &&
(el2 = el.querySelector('option[value="' + cookie + '"]'))) {
el2.setAttribute('selected', 'selected');
}
}
}
}
qrForm.appendChild(row);
}
this.btn = qrForm.querySelector('input[type="submit"]');
this.btn.previousSibling.className = 'presubmit';
this.btn.tabIndex += 20;
if (el = postForm.querySelector('.desktop > label > input[name="spoiler"]')) {
spoiler = document.createElement('span');
spoiler.id = 'qrSpoiler';
spoiler.innerHTML = '<label>[<input type="checkbox" tabindex="'
+ (el.tabIndex + 20) + '" 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) {
if (window.captchaReady) {
if (QR.captchaInterval === null) {
QR.onCaptchaReady();
}
else {
QR.reloadCaptcha();
}
}
else {
window.loadRecaptcha();
}
}
if (!Main.hasMobileLayout) {
Draggable.set($.id('qrHeader'));
}
};
QR.onCaptchaReady = function() {
if (!$.id('qrCaptchaContainer')) {
QR.captchaInterval = 1;
return;
}
QR.pollCaptcha();
};
QR.onFileChange = function(e) {
var fsize, maxFilesize;
QR.needPreuploadCaptcha = false;
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();
}
if (fsize >= QR.preuploadSizeLimit) {
QR.needPreuploadCaptcha = true;
}
}
else {
QR.hidePostError();
}
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.lenCheckTimeout);
QR.lenCheckTimeout = setTimeout(QR.checkComLength, 500);
};
QR.checkComLength = function() {
var byteLength, qrError;
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');
}
}
};
QR.close = function() {
var el, cnt = $.id('quickReply');
QR.comField = null;
QR.currentTid = null;
clearInterval(QR.captchaInterval);
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 (window.RecaptchaState) {
Recaptcha.destroy();
window.captchaReady = false;
if (el = $.id('captchaContainer')) {
el.innerHTML = '<div class="placeholder">'
+ el.getAttribute('data-placeholder') + '</div>';
}
}
document.body.removeChild(cnt);
};
QR.cloneCaptcha = function() {
var row = $.id('qrCaptchaContainer');
if (!row) {
return false;
}
row.innerHTML = '<img id="qrCaptcha" title="Reload" width="300" height="57" src="'
+ $.id('recaptcha_challenge_image').src + '" alt="reCAPTCHA challenge image">'
+ (window.preupload_captcha ? '<input id="qrCapToken" type="hidden" name="captcha_token" disabled>' : '')
+ '<input id="qrCapField" tabindex="25" name="recaptcha_response_field" '
+ 'placeholder="Type the text (Required)" '
+ 'type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">'
+ '<input id="qrChallenge" name="recaptcha_challenge_field" type="hidden" value="'
+ $.id('recaptcha_challenge_field').value + '">';
return true;
};
QR.reloadCaptcha = function(focus) {
var pulse, poll;
if (QR.noCaptcha || !$.id('recaptcha_image') || !window.RecaptchaState) {
return;
}
poll = function() {
var el;
clearTimeout(pulse);
if (el = $.id('recaptcha_challenge_image')) {
QR.captchaInterval = setInterval(QR.cloneCaptcha, QR.captchaDelay);
QR.cloneCaptcha();
if (focus) {
$.id('qrCapField').focus();
}
}
else {
pulse = setTimeout(poll, 100);
}
};
clearInterval(QR.captchaInterval);
Recaptcha.destroy();
window.loadRecaptcha();
pulse = setTimeout(poll, 100);
};
QR.pollCaptcha = function() {
clearTimeout(QR.captchaPollTimeout);
if ($.id('recaptcha_challenge_image')) {
QR.captchaInterval = setInterval(QR.cloneCaptcha, QR.captchaDelay);
QR.cloneCaptcha();
}
else {
QR.captchaPollTimeout = setTimeout(QR.pollCaptcha, 100);
}
};
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 'qrCaptcha':
QR.reloadCaptcha(true);
break;
case 'qrClose':
QR.close();
break;
}
}
};
QR.submit = function(force) {
if (force) {
QR.submitDirect(true);
}
else if (!QR.noCaptcha && window.preupload_captcha && QR.needPreuploadCaptcha) {
QR.submitPreupload();
}
else {
QR.submitDirect();
}
};
QR.showPostError = function(msg, type, silent) {
var qrError;
qrError = $.id('qrError');
if (!qrError) {
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;
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.submitPreupload = function() {
var token, challenge, response, data;
if (!QR.presubmitChecks()) {
return;
}
challenge = $.id('qrChallenge');
response = $.id('qrCapField');
if (response.value == '') {
QR.showPostError('You forgot to type in the CAPTCHA.');
response.focus();
return;
}
data = new FormData();
data.append('mode', 'checkcaptcha');
data.append('challenge', challenge.value);
data.append('response', response.value);
QR.xhr = new XMLHttpRequest();
QR.xhr.open('POST', document.forms.post.action, true);
QR.xhr.onerror = function() {
QR.xhr = null;
QR.submitDirect();
};
QR.xhr.onload = function() {
var el, resp;
QR.xhr = null;
try {
resp = JSON.parse(this.responseText);
}
catch(e) {
console.log("Couldn't verify captcha.");
QR.submitDirect();
return;
}
if (resp.token) {
el = $.id('qrCapToken');
el.value = resp.token;
el.removeAttribute('disabled');
QR.submitDirect();
}
else if (resp.error) {
QR.reloadCaptcha();
QR.btn.value = 'Post';
QR.showPostError(resp.error);
}
else {
if (resp.fail) {
console.log(resp.fail);
}
QR.submitDirect();
}
};
token = $.id('qrCapToken');
token.value = '';
token.setAttribute('disabled', '1');
QR.btn.value = 'Sending';
QR.xhr.send(data);
};
QR.submitDirect = function(force) {
var field, formdata, file;
QR.hidePostError();
if (!QR.presubmitChecks(force)) {
return;
}
QR.auto = false;
if (!force && (field = $.id('qrCapField')) && field.value == '') {
QR.showPostError('You forgot to type in the CAPTCHA.');
field.focus();
return;
}
QR.xhr = new XMLHttpRequest();
QR.xhr.open('POST', document.forms.qrPost.action, true);
QR.xhr.withCredentials = true;
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 (resp = this.responseText.match(/"errmsg"[^>]*>(.*?)<\/span/)) {
QR.reloadCaptcha(true);
QR.showPostError(resp[1]);
return;
}
if (ids = this.responseText.match(/<!-- thread:([0-9]+),no:([0-9]+) -->/)) {
tid = ids[1];
pid = ids[2];
QR.lastTid = tid;
localStorage.setItem('4chan-cd-' + Main.board + '-tid', tid);
hasFile = (el = $.id('qrFile')) && el.value;
QR.setPostTime();
if (Config.persistentQR) {
$.byName('com')[1].value = '';
if (el = $.byName('spoiler')[2]) {
el.checked = false;
}
QR.reloadCaptcha();
if (hasFile) {
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(tid) || {};
tracked['>>' + pid] = 1;
Parser.saveTrackedReplies(tid, tracked);
}
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);
clearInterval(QR.pulse);
QR.btn.value = 'Sending';
QR.xhr.send(formdata);
};
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;
}
return true;
};
QR.getCooldown = function(type) {
if (QR.currentTid != QR.lastTid) {
return QR.cooldowns[type];
}
else {
return QR.cooldowns[type + '_intra'];
}
};
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.floor((QR.activeDelay - QR.cdElapsed) / 1000);
if (QR.cooldown <= 0 || QR.cdElapsed < 0) {
QR.cooldown = false;
QR.removePostTime(type);
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.floor((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) {
var sa = $.id('sa' + tid);
return !sa || sa.hasAttribute('data-hidden');
};
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);
sa.removeAttribute('data-hidden');
if (Main.hasMobileLayout) {
sa.textContent = 'Hide';
$.removeClass(sa, 'mobile-tu-show');
$.cls('postLink', th)[0].appendChild(sa);
th.style.display = null;
$.removeClass(th.nextElementSibling, 'mobile-hr-hidden');
}
else {
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 = $.id('sa' + tid);
sa.setAttribute('data-hidden', tid);
sa.textContent = 'Show Hidden Thread';
$.addClass(sa, '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() {
for (var 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.load();
};
ReplyHiding.isHidden = function(pid) {
var sa = $.id('sa' + pid);
return !sa || sa.hasAttribute('data-hidden');
};
ReplyHiding.toggle = function(pid) {
if (this.isHidden(pid)) {
this.show(pid);
}
else {
this.hide(pid);
}
this.save();
};
ReplyHiding.show = function(pid) {
var post, sa;
post = $.id('pc' + pid);
$.removeClass(post, 'post-hidden');
sa = $.id('sa' + pid);
sa.removeAttribute('data-hidden');
sa.firstChild.src = Main.icons.minus;
delete this.hidden[pid];
};
ReplyHiding.hide = function(pid) {
var post, sa;
post = $.id('pc' + pid);
post.className += ' post-hidden';
sa = $.id('sa' + pid);
sa.setAttribute('data-hidden', pid);
sa.firstChild.src = Main.icons.plus;
this.hidden[pid] = Date.now();
};
ReplyHiding.load = function() {
var storage;
if (storage = localStorage.getItem('4chan-hide-r-' + Main.board)) {
this.hidden = 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];
}
}
this.save();
};
ReplyHiding.save = function() {
for (var i in this.hidden) {
localStorage.setItem('4chan-hide-r-' + Main.board,
JSON.stringify(this.hidden)
);
return;
}
localStorage.removeItem('4chan-hide-r-' + Main.board);
};
/**
* Thread watcher
*/
var ThreadWatcher = {};
ThreadWatcher.init = function() {
var cnt, jumpTo, rect, el;
this.listNode = null;
this.charLimit = 45;
this.watched = {};
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 (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' && e.newValue != e.oldValue) {
ThreadWatcher.watched = JSON.parse(e.newValue);
ThreadWatcher.build(true);
}
};
ThreadWatcher.load = function() {
if (storage = localStorage.getItem('4chan-watch')) {
this.watched = JSON.parse(storage);
}
};
ThreadWatcher.build = function(rebuildButtons) {
var i, html, tuid, key, nodes, 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 {
if (this.watched[key][3]) {
cls = 'archivelink';
}
else {
cls = false;
}
if (this.watched[key][2]) {
html += ' class="' + (cls ? (cls + ' ') : '')
+ 'hasNewReplies">(' + this.watched[key][2] + ') ';
}
else {
html += (cls ? ('class="' + cls + '"') : '') + '>';
}
}
html += '/' + tuid[1] + '/ - ' + this.watched[key][0] + '</a></li>';
}
if (rebuildButtons) {
ThreadWatcher.rebuildButtons();
}
ThreadWatcher.listNode.innerHTML = html;
};
ThreadWatcher.rebuildButtons = function() {
var i, buttons, key;
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.refresh();
}
else if (t.id == 'twClose') {
ThreadWatcher.toggleList();
}
};
ThreadWatcher.toggle = function(tid, board, synced) {
var i, key, label, lastReply, thread;
key = tid + '-' + (board || Main.board);
if (this.watched[key]) {
delete this.watched[key];
}
else {
if (label = $.cls('subject', $.id('pi' + tid))[0].textContent) {
label = label.slice(0, this.charLimit);
}
else if (label = $.id('m' + tid).innerHTML) {
label = label.replace(/(?:<br>)+/g, ' ')
.replace(/<[^>]*?>/g, '').slice(0, this.charLimit);
}
else {
label = 'No.' + tid;
}
if ((thread = $.id('t' + tid)).children[1]) {
lastReply = thread.lastElementChild.id.slice(2);
}
else {
lastReply = tid;
}
this.watched[key] = [ label, lastReply, 0 ];
}
this.save();
this.load();
this.build(true);
};
ThreadWatcher.save = function() {
ThreadWatcher.sortByBoard();
localStorage.setItem('4chan-watch', JSON.stringify(ThreadWatcher.watched));
};
ThreadWatcher.sortByBoard = function() {
var i, self, key, sorted, keys;
self = ThreadWatcher;
sorted = {};
keys = [];
for (key in self.watched) {
keys.push(key);
}
keys.sort(function(a, b) {
a = a.split('-')[1];
b = b.split('-')[1];
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
});
for (i = 0; key = keys[i]; ++i) {
sorted[key] = self.watched[key];
}
self.watched = sorted;
};
ThreadWatcher.canAutoRefresh = function() {
var time;
if (time = localStorage.getItem('4chan-tw-timestamp')) {
return Date.now() - (+time) >= 60000;
}
return false;
};
ThreadWatcher.setRefreshTimestamp = function() {
localStorage.setItem('4chan-tw-timestamp', Date.now());
};
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.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.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, method;
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;
if (this.status == 200) {
posts = Parser.parseThreadJSON(this.responseText);
lastReply = ThreadWatcher.watched[key][1];
newReplies = 0;
for (i = posts.length - 1; i >= 1; i--) {
if (posts[i].no <= lastReply) {
break;
}
++newReplies;
}
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;
};
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, com, 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.jsMath) {
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;
ThreadExpansion.fetch(tid);
}
};
ThreadExpansion.fetch = function(tid) {
$.get('//a.4cdn.org/' + Main.board + '/thread/' + tid + '.json',
{
onload: function() {
var i, p, n, frag, thread, tail, posts, count, msg, metacap,
expmsg, summary, abbr;
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.jsMath) {
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() {
$.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) {
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.lastModified = '0';
this.lastReply = null;
this.currentIcon = null;
this.iconPath = '//s.4cdn.org/image/';
this.iconNode = document.head.querySelector('link[rel="shortcut icon"]');
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.adRefreshDelay = 1000;
this.adDebounce = 0;
this.ads = {};
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.initAds();
this.initControls();
document.addEventListener('scroll', this.onScroll, false);
if (Config.alwaysAutoUpdate || sessionStorage.getItem('4chan-auto-' + Main.tid)) {
this.start();
}
};
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.textContent = 'Update';
btn.setAttribute('data-cmd', 'update');
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);
$.id('mpostform').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() {
var i, j, frag, el, label, navlinks;
// 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() {
this.auto = this.hadAuto = true;
this.autoNode.checked = this.autoNodeBot.checked = true;
this.force = this.updating = false;
this.lastUpdated = Date.now();
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(e) {
var self = ThreadUpdater;
if (document[self.hidden] && self.delayId < self.delayIdHidden) {
self.delayId = self.delayIdHidden;
}
else {
self.delayId = 0;
self.refreshAds();
}
self.timeLeft = self.delayRange[0];
self.lastUpdated = Date.now();
clearTimeout(self.interval);
self.pulse();
};
ThreadUpdater.onScroll = function(e) {
if (ThreadUpdater.hadAuto &&
(document.documentElement.scrollHeight
<= (window.innerHeight + window.pageYOffset)
&& !document[ThreadUpdater.hidden])) {
ThreadUpdater.clearUnread();
}
ThreadUpdater.refreshAds();
};
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() {
var self, now = Date.now();
self = ThreadUpdater;
if (self.updating) {
return;
}
clearTimeout(self.interval);
self.updating = true;
self.setStatus('Updating...');
$.get('//a.4cdn.org/' + Main.board + '/thread/' + Main.tid + '.json',
{
onload: self.onload,
onerror: self.onerror
},
{
'If-Modified-Since': self.lastModified
}
);
};
ThreadUpdater.initAds = function() {
var i, id, adIds = [ '_top_ad', '_middle_ad', '_bottom_ad' ];
for (i = 0; id = adIds[i]; ++i) {
ThreadUpdater.ads[id] = {
time: 0,
seenOnce: false,
isStale: false
};
}
};
ThreadUpdater.invalidateAds = function() {
var id, self = ThreadUpdater;
for (id in self.ads) {
meta = self.ads[id];
if (meta.seenOnce) {
meta.isStale = true;
}
}
};
ThreadUpdater.refreshAds = function() {
var self, now, el, id, ad, meta, hidden, docHeight, offset;
self = ThreadUpdater;
now = Date.now();
if (now - self.adDebounce < 100) {
return;
}
self.adDebounce = now;
hidden = document[self.hidden];
docHeight = document.documentElement.clientHeight;
for (id in self.ads) {
meta = self.ads[id];
if (hidden) {
continue;
}
ad = window[id];
if (!ad) {
continue;
}
el = $.id(ad.D);
if (!el) {
continue;
}
offset = el.getBoundingClientRect();
if (offset.top < 0 || offset.bottom > docHeight) {
continue;
}
meta.seenOnce = true;
if (!meta.isStale || now - meta.time < self.adRefreshDelay) {
continue;
}
meta.time = now;
meta.isStale = false;
ados_refresh(ad, 0, false);
}
};
ThreadUpdater.markDeletedReplies = function(newposts) {
var i, j, posthash, oldposts, el;
posthash = {};
for (i = 0; j = newposts[i]; ++i) {
posthash['pc' + j.no] = 1;
}
oldposts = $.cls('replyContainer');
for (i = 0; j = oldposts[i]; ++i) {
if (!posthash[j.id] && !$.hasClass(j, 'deleted')) {
if (this.deletionQueue[j.id]) {
el = document.createElement('img');
el.src = Main.icons2.trash;
el.className = 'trashIcon';
el.title = 'This post has been deleted';
$.addClass(j, 'deleted');
$.cls('postNum', j)[1].appendChild(el);
delete this.deletionQueue[j.id];
}
else {
this.deletionQueue[j.id] = 1;
}
}
}
};
ThreadUpdater.onload = function() {
var i, el, state, self, nodes, thread, newposts, frag, lastrep, lastid,
spoiler, op, doc, autoscroll, count, fromQR, lastRepPos;
self = ThreadUpdater;
nodes = [];
self.setStatus('');
if (this.status == 200) {
self.lastModified = this.getResponseHeader('Last-Modified');
thread = $.id('t' + Main.tid);
lastrep = thread.children[thread.childElementCount - 1];
lastid = +lastrep.id.slice(2);
newposts = Parser.parseThreadJSON(this.responseText);
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 (!fromQR) {
self.markDeletedReplies(newposts);
}
if (count) {
doc = document.documentElement;
autoscroll = (
Config.autoScroll
&& document[self.hidden]
&& doc.scrollHeight == (window.innerHeight + window.pageYOffset)
);
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.bumplimit, op.imagelimit);
}
self.invalidateAds();
self.refreshAds();
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) {
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 i, cnt;
this.nodeTop = document.createElement('div');
this.nodeTop.className = 'thread-stats';
this.nodeBot = this.nodeTop.cloneNode(false);
cnt = $.cls('navLinks');
cnt[1] && cnt[1].appendChild(this.nodeTop);
cnt[2] && cnt[2].appendChild(this.nodeBot);
this.pageNumber = null;
this.update(null, null, window.bumplimit, window.imagelimit);
if (!window.thread_archived) {
this.updatePageNumber();
this.pageInterval = setInterval(this.updatePageNumber, 3 * 60000);
}
};
ThreadStats.update = function(replies, images, isBumpFull, isImageFull) {
var stats, repStr, imgStr, pageStr, stateStr;
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 data-tip="Bump limit reached">' + replies + '</em>');
}
else {
stats.push('<span data-tip="Replies">' + replies + '</span>');
}
if (isImageFull) {
stats.push('<em data-tip="Image limit reached">' + images + '</em>');
}
else {
stats.push('<span data-tip="Images">' + images + '</span>');
}
if (!window.thread_archived) {
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, 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');
nodes[0].textContent = nodes[1].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.exec = function(cnt, pi, msg, tid) {
var 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) {
cnt.className += ' post-hidden';
el = document.createElement('span');
if (!tid) {
el.textContent = '[View]';
el.setAttribute('data-filtered', '1');
}
else {
el.innerHTML = '[<a 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.load = function() {
var i, j, w, f, rawFilters, rawPattern, fid, regexEscape, regexType,
wordSepS, wordSepE, words, inner, regexWildcard, replaceWildcard;
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
});
}
}
}
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><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>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, menu, html, rawFilters, filterId, 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><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>Order</th>\
<th>On</th>\
<th>Pattern</th>\
<th>Boards</th>\
<th>Type</th>\
<th>Color</th>\
<th>Hide</th>\
<th>Del</th>\
</tr></thead><tbody id="filter-list"></tbody><tfoot><tr><td colspan="8">\
<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: '',
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,
hide: tr.children[6].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-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>';
// 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 {
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 - 100;
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 = document.createElement('embed');
el.setAttribute('allowScriptAccess', 'never');
el.type = 'application/x-shockwave-flash';
el.width = width;
el.height = height;
el.src = link.href;
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;
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 = document.createElement('embed');
el.setAttribute('allowScriptAccess', 'never');
el.src = e.target.href;
el.width = '100%';
el.height = '100%';
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.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);
}
};
/**
* 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<]*/g;
this.toggleYT = /(?:v=|\.be\/)([a-zA-Z0-9_-]{11})/;
this.timeYT = /#t=([ms0-9]+)/;
this.matchVocaroo = /vocaroo\.com\/i\/([a-z0-9]{12})/gi;
this.map = {
yt: this.toggleYouTube,
sc: this.toggleSoundCloud,
vocaroo: this.toggleVocaroo
};
};
Media.parseSoundCloud = function(msg) {
msg.innerHTML = msg.innerHTML.replace(this.matchSC, this.replaceSoundCloud);
};
Media.replaceSoundCloud = function(link) {
return '<span>' + link + '</span> [<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);
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) {
return '<span>' + link + '</span> [<a href="javascript:;" data-cmd="embed" data-type="yt">Embed</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 += '#t=' + encodeURIComponent(time);
}
el = document.createElement('div');
el.className = 'media-embed';
el.innerHTML = '<iframe src="//www.youtube.com/embed/'
+ vid
+ '" width="640" height="360" frameborder="0"></iframe>'
node.parentNode.insertBefore(el, node.nextElementSibling);
node.textContent = 'Remove';
}
else {
node.textContent = 'Error';
}
}
};
Media.parseVocaroo = function(msg) {
msg.innerHTML = msg.innerHTML.replace(this.matchVocaroo, this.replaceVocaroo);
};
Media.replaceVocaroo = function(link) {
return '<span>' + link + '</span> [<a href="javascript:;" data-cmd="embed" data-type="vocaroo">Embed</a>]';
};
Media.toggleVocaroo = 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(Media.matchVocaroo);
if (vid && (vid = vid[0].split('/').pop())) {
vid = encodeURIComponent(vid);
el = document.createElement('div');
el.className = 'media-embed';
el.innerHTML = '<embed width="220" height="140" class="media-embed" '
+ 'src="//vocaroo.com/mediafoo.swf?playMediaID=' + vid + '&autoplay=0">';
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);
}
};
/**
* 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><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 = {};
Keybinds.init = function() {
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 (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><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();
}
};
/**
* Reporting
*/
var Report = {
init: function() {
window.addEventListener('message', Report.onMessage, false);
}
};
Report.onMessage = function(e) {
var id;
if (e.origin === 'https://sys.4chan.org' && /^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) {
window.open('https://sys.4chan.org/'
+ (board || Main.board) + '/imgboard.php?mode=report&no=' + pid
, Date.now(),
"toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=600,height=170");
};
/**
* Custom Menu
*/
var CustomMenu = {};
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, j, el, cntBottom, board, navs, boardList, more;
if (!str) {
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.4chan.org/' + board + '/';
cnt.appendChild(el);
}
cnt.appendChild(document.createTextNode(']'));
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();
}
};
CustomMenu.showEditor = function() {
var cnt;
cnt = document.createElement('div');
cnt.id = 'customMenu';
cnt.className = 'UIPanel';
cnt.setAttribute('data-close', '1');
cnt.innerHTML = '\
<div class="extPanel reply"><div class="panelHeader">Custom Board List\
<span><img alt="Close" title="Close" class="pointer" data-close="1" src="'
+ Main.icons.cross + '"></a></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() {
var input;
if (input = $.id('customMenuBox')) {
Config.customMenuList = input.value;
}
CustomMenu.closeEditor();
};
/**
* Draggable helper
*/
var Draggable = {
el: null,
key: null,
scrollX: null,
scrollY: null,
dx: null, dy: null, right: null, bottom: null,
set: function(handle) {
handle.addEventListener('mousedown', Draggable.startDrag, false);
},
unset: function(handle) {
handle.removeEventListener('mousedown', Draggable.startDrag, false);
},
startDrag: function(e) {
var self, doc, offs;
if (this.parentNode.hasAttribute('data-shiftkey') && !e.shiftKey) {
return;
}
e.preventDefault();
self = Draggable;
doc = document.documentElement;
self.el = this.parentNode;
self.key = self.el.getAttribute('data-trackpos');
offs = self.el.getBoundingClientRect();
self.dx = e.clientX - offs.left;
self.dy = e.clientY - offs.top;
self.right = doc.clientWidth - offs.width;
self.bottom = doc.clientHeight - offs.height;
if (getComputedStyle(self.el, null).position != 'fixed') {
self.scrollX = window.pageXOffset;
self.scrollY = window.pageYOffset;
}
else {
self.scrollX = self.scrollY = 0;
}
document.addEventListener('mouseup', self.endDrag, false);
document.addEventListener('mousemove', self.onDrag, false);
},
endDrag: function(e) {
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 < 1) {
style.top = '0';
style.bottom = '';
}
else if (Draggable.bottom < top) {
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;
this.hasDragAndDrop = false; /*'draggable' in document.createElement('div');*/
};
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,
imageExpansion: true,
fitToScreenExpansion: false,
threadExpansion: true,
alwaysDepage: false,
localTime: true,
stickyNav: false,
keyBinds: false,
inlineQuotes: 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,
classicNav: false,
fixedThreadWatcher: false,
persistentQR: false,
forceHTTPS: false,
reportButton: false,
disableAll: false
};
var ConfigMobile = {
embedYouTube: false,
compactThreads: false
};
Config.load = function() {
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() {
localStorage.setItem('4chan-settings', JSON.stringify(Config));
if (Config.forceHTTPS) {
Main.setCookie('https', 1);
}
else {
Main.removeCookie('https');
}
};
/**
* 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 ],
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' ],
},
'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 ],
customMenu: [ 'Custom board list [<a href="javascript:;" data-cmd="custom-menu-edit">Edit</a>]', 'Only show selected boards in top and bottom board lists' ],
alwaysDepage: [ 'Always use infinite scroll', 'Enable infinite scroll by default, so reaching the bottom of the board index will load subsequent pages' ],
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' ],
revealSpoilers: [ "Don't spoiler images", 'Show image thumbnail and original filename instead of spoiler placeholders' ],
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' ],
embedVocaroo: [ 'Embed Vocaroo links', 'Embed Vocaroo player into replies' ]
},
'Miscellaneous': {
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 ],
reportButton: [ 'Report button', 'Add a report button next to posts for easy reporting', true, false, true ],
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;
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();
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><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, '') + '#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><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) {
var el;
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, i, j;
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);
}
};
/**
* 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.init = function() {
var params;
document.addEventListener('DOMContentLoaded', Main.run, false);
Main.now = Date.now();
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';
}
Main.passEnabled = Main.getCookie('pass_enabled');
QR.noCaptcha = QR.noCaptcha || Main.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];
Report.init();
if (Config.IDColor) {
IDColor.init();
}
if (Config.customCSS) {
CustomCSS.init();
}
if (Config.keyBinds) {
Keybinds.init();
}
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.4chan.org" 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.run = function() {
var 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);
if (Config.disableAll) {
return;
}
Main.hasMobileLayout = Main.checkMobileLayout();
Main.isMobileDevice = /Mobile|Android|Dolfin|Opera Mobi|PlayStation Vita|Nintendo DS/.test(navigator.userAgent);
if (Main.hasMobileLayout) {
$.extend(Config, ConfigMobile);
}
else {
$.id('bottomReportBtn').style.display = 'none';
if (Main.isMobileDevice) {
$.addClass(document.body, 'isMobileDevice');
}
}
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.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);
}
if (Config.quotePreview || Config.imageHover|| Config.filter) {
thread = $.id('delform');
thread.addEventListener('mouseover', Main.onThreadMouseOver, false);
thread.addEventListener('mouseout', Main.onThreadMouseOut, false);
}
if (!Main.hasMobileLayout) {
Main.initGlobalMessage();
}
if (Config.stickyNav) {
Main.setStickyNav();
}
if (Config.threadExpansion) {
ThreadExpansion.init();
}
if (Config.threadWatcher) {
ThreadWatcher.init();
}
if (Config.filter) {
Filter.init();
}
if (Config.embedSoundCloud || Config.embedYouTube || Config.embedVocaroo) {
Media.init();
}
ReplyHiding.init();
if (Config.quotePreview) {
QuotePreview.init();
}
Parser.init();
if (Main.tid) {
Main.threadClosed = !document.forms.post;
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();
};
Main.isThreadClosed = function(tid) {
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.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'));
}
};
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) {
var date = new Date();
date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000));
document.cookie = name + '=' + value
+ '; expires=' + date.toGMTString()
+ '; path=/; domain=boards.4chan.org';
};
Main.removeCookie = function(name) {
document.cookie = name + '='
+ '; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
+ '; path=/; domain=boards.4chan.org';
};
Main.onclick = function(e) {
var t, cmd, tid;
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':
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 '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':
CustomMenu.showEditor();
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('/')[1];
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')) {
if (ImageExpansion.toggle(t)) {
e.preventDefault();
}
}
else if (Config.inlineQuotes && e.which == 1 && $.hasClass(t, 'quotelink')) {
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) {
if ($.hasClass(t, 'quotelink')
&& (cmd = t.getAttribute('href').match(QuotePreview.regex))
&& cmd[1] != 'rs') {
e.preventDefault();
}
}
}
};
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')) {
ImageHover.show(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')) {
ImageHover.hide();
}
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;\
}\
div.post div.postInfo {\
width: auto;\
display: inline;\
}\
.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;\
}\
#quickReport,\
#quickReply {\
display: block;\
position: fixed;\
padding: 2px;\
font-size: 10pt;\
}\
#qrepHeader,\
#qrHeader {\
text-align: center;\
margin-bottom: 1px;\
padding: 0;\
height: 18px;\
line-height: 18px;\
}\
#qrepClose,\
#qrClose {\
float: right;\
}\
#quickReport iframe {\
overflow: hidden;\
}\
#quickReport {\
height: 190px;\
}\
#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;\
}\
#quickReply textarea {\
min-width: 296px;\
float: left;\
}\
#quickReply input::-moz-placeholder,\
#quickReply textarea::-moz-placeholder {\
color: #aaa !important;\
opacity: 1 !important;\
}\
#quickReply input[type="submit"] {\
width: 83px;\
margin: 0;\
font-size: 10pt;\
float: left;\
}\
#quickReply #qrCapField {\
display: block;\
margin-top: 1px;\
}\
#qrCaptcha {\
width: 300px;\
height: 53px;\
cursor: pointer;\
border: 1px solid #aaa;\
display: block;\
}\
#quickReply input.presubmit {\
margin-right: 1px;\
width: 212px;\
float: left;\
}\
#qrFile {\
width: 215px;\
margin-right: 5px;\
}\
.qrRealFile {\
position: absolute;\
left: 0;\
visibility: hidden;\
}\
.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;\
}\
.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;\
}\
.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;\
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 span {\
position: absolute;\
right: 5px;\
top: 5px;\
}\
.UIMenu,\
.UIPanel {\
position: fixed;\
width: 100%;\
height: 100%;\
z-index: 9002;\
top: 0;\
left: 0;\
}\
.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;\
}\
#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;\
}\
#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: default;\
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;\
}\
.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;\
}\
.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 !important;\
}\
.tomorrow .postMenuBtn:hover {\
color: #81a2be !important;\
}\
.photon .postMenuBtn {\
color: #FF6600 !important;\
}\
.photon .postMenuBtn:hover {\
color: #FF3300 !important;\
}\
.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 .expandedWebm {\
float: none;\
}\
.centeredThreads .summary {\
margin-left: 12.5%;\
display: block;\
}\
.centre-exp div.op{\
display: table;\
}\
#yt-preview { position: absolute; }\
#yt-preview img { display: block; }\
\
@media only screen and (max-width: 480px) {\
#threadWatcher {\
max-width: none;\
padding: 3px 0;\
left: 0;\
width: 100%;\
border-left: none;\
border-right: none;\
}\
#watchList {\
padding: 0 10px;\
}\
.btn-row {\
margin-top: 5px;\
}\
.image-expanded .mFileInfo {\
display: none !important;\
}\
.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: 5px 5px 0 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;\
}\
}\
';
style = document.createElement('style');
style.setAttribute('type', 'text/css');
style.textContent = css;
document.head.appendChild(style);
};
Main.init();