8967 lines
211 KiB
JavaScript
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(/&/g, '&')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, "'")
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
};
|
|
|
|
Parser.encodeSpecialChars = function(str) {
|
|
return str.replace(/&/g, '&')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
};
|
|
|
|
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 = ' <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 + '">>></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 = '>>>/' + board + '/';
|
|
}
|
|
else {
|
|
prelink = '';
|
|
pretext = '>>';
|
|
}
|
|
|
|
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">>>' + pid + '</a> ';
|
|
}
|
|
else {
|
|
bl.innerHTML = '<a href="' + href + '" class="quotelink">>>' + 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 »</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] + '">×</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 & 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> — 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> — 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> — place double quotes around the pattern to search for an exact string</li></ul>\
|
|
<ul><li><strong>Wildcards:</strong></li>\
|
|
<li><code>feel*</code> — matches expressions such as <em>"feel"</em>, <em>"feels"</em>, <em>"feeling"</em>, <em>"feeler"</em>, etc…</li>\
|
|
<li><code>idolm*ster</code> — 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> — NOT operator.</li>\
|
|
<li><code>/^>/</code> — comments starting with a quote.</li>\
|
|
<li><code>/^$/</code> — 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 & 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">↑</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, '"') + '"></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">∕';
|
|
}
|
|
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">×</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 = '∕';
|
|
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> — Toggle auto-updater</li>\
|
|
<li><kbd>Q</kbd> — Open Quick Reply</li>\
|
|
<li><kbd>R</kbd> — Update thread</li>\
|
|
<li><kbd>W</kbd> — Watch/Unwatch thread</li>\
|
|
<li><kbd>B</kbd> — Previous page</li>\
|
|
<li><kbd>N</kbd> — Next page</li>\
|
|
<li><kbd>I</kbd> — Return to index</li>\
|
|
<li><kbd>C</kbd> — Open catalog</li>\
|
|
<li><kbd>F</kbd> — Filter selected text</li>\
|
|
</ul><ul>\
|
|
<li><strong>Quick Reply (always enabled)</strong></li>\
|
|
<li><kbd>Ctrl + Click</kbd> the post number — Quote without linking</li>\
|
|
<li><kbd>Ctrl + S</kbd> — Spoiler tags</li>\
|
|
<li><kbd>Esc</kbd> — 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 & 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 & 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 & 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">▼</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();
|