diff --git a/4chan_x.user.js b/4chan_x.user.js
index 3c38f41f7..17a38ce88 100644
--- a/4chan_x.user.js
+++ b/4chan_x.user.js
@@ -8,11 +8,16 @@
// @match *://boards.4chan.org/*
// @match *://images.4chan.org/*
// @match *://sys.4chan.org/*
+// @match *://api.4chan.org/*
// @match *://*.foolz.us/api/*
+// @grant GM_getValue
+// @grant GM_setValue
+// @grant GM_deleteValue
+// @grant GM_openInTab
// @run-at document-start
// @updateURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
// @downloadURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
-// @icon http://mayhemydg.github.com/4chan-x/favicon.gif
+// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7
// ==/UserScript==
/* LICENSE
@@ -81,6 +86,7 @@
Config = {
main: {
Enhancing: {
+ 'Disable 4chan\'s extension': [true, 'Avoid conflicts between 4chan X and 4chan\'s inline extension.'],
'404 Redirect': [true, 'Redirect dead threads and images.'],
'Keybinds': [true, 'Bind actions to keyboard shortcuts.'],
'Time Formatting': [true, 'Localize and format timestamps arbitrarily.'],
@@ -937,6 +943,9 @@
return (_ref2 = $.id('boardNavDesktopFoot')) != null ? _ref2.hidden = true : void 0;
},
initFeatures: function() {
+ if (Conf['Disable 4chan\'s extension']) {
+ localStorage.setItem('4chan-settings', '{"disableAll":true}');
+ }
if (Conf['Resurrect Quotes']) {
try {
Quotify.init();
@@ -1086,7 +1095,7 @@
return $.on(d, 'DOMNodeInserted', Main.addStyle);
}
},
- css: "/* general */\n.dialog.reply {\n display: block;\n border: 1px solid rgba(0, 0, 0, .25);\n padding: 0;\n}\n.move {\n cursor: move;\n}\nlabel {\n cursor: pointer;\n}\na[href=\"javascript:;\"] {\n text-decoration: none;\n}\n.warning {\n color: red;\n}\n\n/* 4chan style fixes */\n.opContainer, .op {\n display: block !important;\n}\n.post {\n overflow: visible !important;\n}\n\n/* header */\nbody.fourchan_x {\n margin-top: 2.5em;\n}\n#boardNavDesktop.reply {\n border-width: 0 0 1px;\n padding: 4px;\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n transition: opacity .1s ease-in-out;\n -o-transition: opacity .1s ease-in-out;\n -moz-transition: opacity .1s ease-in-out;\n -webkit-transition: opacity .1s ease-in-out;\n z-index: 1;\n}\n#boardNavDesktop.reply:not(:hover) {\n opacity: .4;\n transition: opacity 1.5s .5s ease-in-out;\n -o-transition: opacity 1.5s .5s ease-in-out;\n -moz-transition: opacity 1.5s .5s ease-in-out;\n -webkit-transition: opacity 1.5s .5s ease-in-out;\n}\n#boardNavDesktop.reply a {\n margin: -1px;\n}\n#settings {\n float: right;\n}\n\n/* quote */\n.inlined {\n opacity: .5;\n}\n#qp input, .forwarded {\n display: none;\n}\n.quotelink.forwardlink,\n.backlink.forwardlink {\n text-decoration: none;\n border-bottom: 1px dashed;\n}\n.inline {\n border: 1px solid rgba(128, 128, 128, .5);\n display: table;\n margin: 2px 0;\n}\n.inline .post {\n border: 0 !important;\n display: table !important;\n margin: 0 !important;\n padding: 1px 2px !important;\n}\n#qp {\n position: fixed;\n padding: 2px 2px 5px;\n}\n#qp .post {\n border: none;\n margin: 0;\n padding: 0;\n}\n#qp img {\n max-height: 300px;\n max-width: 500px;\n}\n.qphl {\n outline: 2px solid rgba(216, 94, 49, .7);\n}\n\n/* file */\n.fileText:hover .fntrunc,\n.fileText:not(:hover) .fnfull {\n display: none;\n}\n#ihover {\n box-sizing: border-box;\n -moz-box-sizing: border-box;\n max-height: 100%;\n max-width: 75%;\n position: fixed;\n padding-bottom: 16px;\n}"
+ css: "/* general */\n.dialog.reply {\n display: block;\n border: 1px solid rgba(0, 0, 0, .25);\n padding: 0;\n}\n.move {\n cursor: move;\n}\nlabel {\n cursor: pointer;\n}\na[href=\"javascript:;\"] {\n text-decoration: none;\n}\n.warning {\n color: red;\n}\n\n/* 4chan style fixes */\n.opContainer, .op {\n display: block !important;\n}\n.post {\n overflow: visible !important;\n}\n\n/* header */\nbody.fourchan_x {\n margin-top: 2.5em;\n}\n#boardNavDesktop.reply {\n border-width: 0 0 1px;\n padding: 4px;\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n transition: opacity .1s ease-in-out;\n -o-transition: opacity .1s ease-in-out;\n -moz-transition: opacity .1s ease-in-out;\n -webkit-transition: opacity .1s ease-in-out;\n z-index: 1;\n}\n#boardNavDesktop.reply:not(:hover) {\n opacity: .4;\n transition: opacity 1.5s .5s ease-in-out;\n -o-transition: opacity 1.5s .5s ease-in-out;\n -moz-transition: opacity 1.5s .5s ease-in-out;\n -webkit-transition: opacity 1.5s .5s ease-in-out;\n}\n#boardNavDesktop.reply a {\n margin: -1px;\n}\n#settings {\n float: right;\n}\n\n/* quote */\n.quotelink.deadlink {\n text-decoration: underline !important;\n}\n.inlined {\n opacity: .5;\n}\n#qp input, .forwarded {\n display: none;\n}\n.quotelink.forwardlink,\n.backlink.forwardlink {\n text-decoration: none;\n border-bottom: 1px dashed;\n}\n.inline {\n border: 1px solid rgba(128, 128, 128, .5);\n display: table;\n margin: 2px 0;\n}\n.inline .post {\n border: 0 !important;\n display: table !important;\n margin: 0 !important;\n padding: 1px 2px !important;\n}\n#qp {\n position: fixed;\n padding: 2px 2px 5px;\n}\n#qp .post {\n border: none;\n margin: 0;\n padding: 0;\n}\n#qp img {\n max-height: 300px;\n max-width: 500px;\n}\n.qphl {\n outline: 2px solid rgba(216, 94, 49, .7);\n}\n\n/* file */\n.fileText:hover .fntrunc,\n.fileText:not(:hover) .fnfull {\n display: none;\n}\n#ihover {\n box-sizing: border-box;\n -moz-box-sizing: border-box;\n max-height: 100%;\n max-width: 75%;\n position: fixed;\n padding-bottom: 16px;\n}"
};
Redirect = {
@@ -1230,6 +1239,7 @@
};
Build = {
+ spoilerRange: {},
shortFilename: function(filename, isReply) {
var threshold;
threshold = isReply ? 30 : 40;
@@ -1239,120 +1249,139 @@
return filename;
}
},
- post: function(o) {
- var board, bq, capcode, comment, container, date, dateUTC, email, file, fl, flag, flagTitle, html, isOP, name, pi, post, postID, subject, threadID, tripcode, uniqueID;
- postID = o.postID, threadID = o.threadID, board = o.board, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flag = o.flag, flagTitle = o.flagTitle, date = o.date, dateUTC = o.dateUTC, comment = o.comment, file = o.file;
+ postFromObject: function(data, board) {
+ var o;
+ o = {
+ postID: data.no,
+ threadID: data.resto || data.no,
+ board: board,
+ name: data.name,
+ capcode: data.capcode,
+ tripcode: data.trip,
+ uniqueID: data.id,
+ email: data.email ? encodeURIComponent(data.email) : '',
+ subject: data.sub,
+ flagCode: data.country,
+ flagName: data.country_name,
+ date: data.now,
+ dateUTC: data.time,
+ comment: data.com,
+ isSticky: !!data.sticky,
+ isClosed: !!data.closed
+ };
+ if (data.ext || data.filedeleted) {
+ o.file = {
+ name: data.filename + data.ext,
+ timestamp: "" + data.tim + data.ext,
+ url: "//images.4chan.org/" + board + "/src/" + data.tim + data.ext,
+ height: data.h,
+ width: data.w,
+ MD5: data.md5,
+ size: data.fsize,
+ turl: "//thumbs.4chan.org/" + board + "/thumb/" + data.tim + "s.jpg",
+ theight: data.tn_h,
+ twidth: data.tn_w,
+ isSpoiler: !!data.spoiler,
+ isDeleted: !!data.filedeleted
+ };
+ }
+ return Build.post(o);
+ },
+ post: function(o, isArchived) {
+ /*
+ This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS).
+ @license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
+ */
+
+ var a, board, capcode, capcodeClass, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
+ postID = o.postID, threadID = o.threadID, board = o.board, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, file = o.file;
isOP = postID === threadID;
- html = [];
- html.push(" ");
- html.push("" + subject + " ");
- html.push("");
+ staticPath = '//static.4chan.org';
if (email) {
- html.push("");
- }
- html.push("" + name + "");
- if (tripcode) {
- html.push(" " + tripcode + "");
- }
- if (uniqueID) {
- html.push(" (ID: " + uniqueID + ")");
+ emailStart = '';
+ emailEnd = '';
+ } else {
+ emailStart = '';
+ emailEnd = '';
}
+ subject = subject ? "" + subject + "" : '';
+ userID = !capcode && uniqueID ? (" (ID: ") + ("" + uniqueID + ") ") : '';
switch (capcode) {
- case 'M':
- html.push(' ## Mod');
- html.push('
');
+ case 'admin':
+ case 'admin_highlight':
+ capcodeClass = " capcodeAdmin";
+ capcodeStart = " ## Admin";
+ capcode = ("
";
break;
- case 'A':
- html.push(' ## Admin');
- html.push('
');
+ case 'mod':
+ capcodeClass = " capcodeMod";
+ capcodeStart = " ## Moderator";
+ capcode = ("
";
break;
- case 'D':
- html.push(' ## Mod');
- html.push('
');
+ case 'developer':
+ capcodeClass = " capcodeDeveloper";
+ capcodeStart = " ## Developer";
+ capcode = ("
";
+ break;
+ default:
+ capcodeClass = '';
+ capcodeStart = '';
+ capcode = '';
}
- if (email) {
- html.push('');
- }
- if (flag) {
- html.push("
");
- }
- html.push(' ');
- html.push("" + date + " ");
- html.push('');
- html.push("No.");
- html.push("" + postID + "");
- html.push('');
- pi = $.el('div', {
- id: "pi" + postID,
- className: 'postInfo desktop',
- innerHTML: html.join('')
- });
- bq = $.el('blockquote', {
- id: "m" + postID,
- className: 'postMessage',
- innerHTML: comment
- });
- if (file.name) {
- html = [];
- html.push('
');
- html.push("
File: ");
- html.push("" + file.origin + "");
- html.push('-(');
+ flag = flagCode ? ("
") : '';
+ if (file != null ? file.isDeleted : void 0) {
+ fileHTML = isOP ? ("") + ("
") + " " : ("") + ("
") + " ";
+ } else if (file) {
+ ext = file.name.slice(-3);
+ if (!file.twidth && !file.theight && ext === 'gif') {
+ file.twidth = file.width;
+ file.theight = file.height;
+ }
+ fileSize = $.bytesToString(file.size);
+ fileThumb = file.turl;
if (file.isSpoiler) {
- html.push('Spoiler Image, ');
+ fileSize = "Spoiler Image, " + fileSize;
+ if (!isArchived) {
+ fileThumb = '//static.4chan.org/image/spoiler';
+ if (spoilerRange = Build.spoilerRange[board]) {
+ fileThumb += ("-" + board) + Math.floor(1 + spoilerRange * Math.random());
+ }
+ fileThumb += '.png';
+ file.twidth = file.theight = 100;
+ }
}
- html.push("" + ($.bytesToString(file.size)) + ", ");
- html.push(/\.pdf$/i.test(file.name) ? "PDF" : "" + file.width + "x" + file.height);
- if (!file.isSpoiler) {
- html.push(", " + (Build.shortFilename(file.name)) + "");
- }
- html.push(") ");
- html.push("");
- html.push("
");
- html.push('');
- fl = $.el('div', {
- id: "f" + postID,
- className: 'file',
- innerHTML: html.join('')
+ imgSrc = ("") + ("
");
+ a = $.el('a', {
+ innerHTML: file.name
});
+ filename = a.textContent.replace(/%22/g, '"');
+ a.textContent = Build.shortFilename(filename);
+ shortFilename = a.innerHTML;
+ a.textContent = filename;
+ filename = a.innerHTML.replace(/'/g, ''');
+ fileDims = ext === 'pdf' ? 'PDF' : "" + file.width + "x" + file.height;
+ fileInfo = ("File: " + file.timestamp + "") + ("-(" + fileSize + ", " + fileDims + (file.isSpoiler ? '' : ", " + shortFilename + "")) + ")";
+ fileHTML = "" + fileInfo + "
" + imgSrc + "
";
+ } else {
+ fileHTML = '';
}
- post = $.el('div', {
- id: "p" + postID,
- className: "post " + (isOP ? 'op' : 'reply')
- });
- if (fl && isOP) {
- $.add(post, fl);
- }
- $.add(post, pi);
- if (fl && !isOP) {
- $.add(post, fl);
- }
- $.add(post, bq);
+ tripcode = tripcode ? " " + tripcode + "" : '';
+ sticky = isSticky ? '
' : '';
+ closed = isClosed ? '
' : '';
container = $.el('div', {
id: "pc" + postID,
- className: "postContainer " + (isOP ? 'op' : 'reply') + "Container"
+ className: "postContainer " + (isOP ? 'op' : 'reply') + "Container",
+ innerHTML: (isOP ? '' : ">>
") + ("") + ("
") + ("
") + ("" + (name || '') + "") + tripcode + capcodeStart + capcode + userID + flag + sticky + closed + ("
" + subject) + ("" + date) + '
' + ("No.") + ("" + postID + "") + '' + '
' + (isOP ? fileHTML : '') + ("
") + ("
") + ("" + subject + " ") + ("
") + emailStart + ("" + (name || '') + "") + tripcode + capcodeStart + emailEnd + capcode + userID + flag + sticky + closed + ' ' + ("
" + date + " ") + "
" + ("No.") + ("" + postID + "") + '' + '
' + (isOP ? '' : fileHTML) + ("
" + (comment || '') + "
") + '
'
});
- if (!isOP) {
- $.add(container, $.el('div', {
- id: "sa" + postID,
- className: 'sideArrows',
- textContent: '>>'
- }));
+ _ref = $$('.quotelink', container);
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ quote = _ref[_i];
+ href = quote.getAttribute('href');
+ if (href[0] === '/') {
+ continue;
+ }
+ quote.href = "/" + board + "/res/" + href;
}
- $.add(container, post);
return container;
}
};
@@ -1397,7 +1426,7 @@
}
root.textContent = "Loading post No." + postID + "...";
if (threadID) {
- return $.cache("/" + board + "/res/" + threadID, function() {
+ return $.cache("//api.4chan.org/" + board + "/res/" + threadID + ".json", function() {
return Get.fetchedPost(this, board, threadID, postID, root, context);
});
} else if (url = Redirect.post(board, postID)) {
@@ -1420,7 +1449,7 @@
return $.add(root, nodes.root);
},
fetchedPost: function(req, board, threadID, postID, root, context) {
- var doc, href, link, pc, post, quote, status, thread, url, _i, _len, _ref;
+ var post, posts, spoilerRange, status, thread, url, _i, _len;
if (post = g.posts["" + board + "." + postID]) {
Get.insert(post, root, context);
return;
@@ -1429,48 +1458,44 @@
if (status !== 200) {
if (url = Redirect.post(board, postID)) {
$.cache(url, function() {
- return Get.archivedPost(this, board, postID, root);
+ return Get.archivedPost(this, board, postID, root, context);
});
} else {
$.addClass(root, 'warning');
- root.textContent = status === 404 ? "Thread No." + threadID + " has not been found." : "Error " + req.status + ": " + req.statusText + ".";
+ root.textContent = status === 404 ? "Thread No." + threadID + " 404'd." : "Error " + req.status + ": " + req.statusText + ".";
}
return;
}
- doc = d.implementation.createHTMLDocument('');
- doc.documentElement.innerHTML = req.response;
- if (!(pc = doc.getElementById("pc" + postID))) {
- if (url = Redirect.post(board, postID)) {
- $.cache(url, function() {
- return Get.archivedPost(this, board, postID, root);
- });
- } else {
- $.addClass(root, 'warning');
- root.textContent = "Post No." + postID + " has not been found.";
- }
- return;
+ posts = JSON.parse(req.response).posts;
+ if (spoilerRange = posts[0].custom_spoiler) {
+ Build.spoilerRange[board] = spoilerRange;
}
- pc = d.importNode(pc, true);
- _ref = $$('.quotelink', pc);
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- quote = _ref[_i];
- href = quote.getAttribute('href');
- if (href[0] === '/') {
- continue;
+ postID = +postID;
+ for (_i = 0, _len = posts.length; _i < _len; _i++) {
+ post = posts[_i];
+ if (post.no === postID) {
+ break;
+ }
+ if (post.no > postID) {
+ if (url = Redirect.post(board, postID)) {
+ $.cache(url, function() {
+ return Get.parseArchivedPost(this, board, postID, root, context);
+ });
+ } else {
+ $.addClass(root, 'warning');
+ root.textContent = "Post No." + postID + " was not found.";
+ }
+ return;
}
- quote.href = "/" + board + "/res/" + href;
}
- link = $('a[title="Highlight this post"]', pc);
- link.href = "/" + board + "/res/" + threadID + "#p" + postID;
- link.nextSibling.href = "/" + board + "/res/" + threadID + "#q" + postID;
board = g.boards[board] || new Board(board);
thread = g.threads["" + board + "." + threadID] || new Thread(threadID, board);
- post = new Post(pc, thread, board);
+ post = new Post(Build.postFromObject(post, board), thread, board);
Main.callbackNodes(Post, [post]);
return Get.insert(post, root, context);
},
archivedPost: function(req, board, postID, root, context) {
- var bq, comment, data, post, postContainer, thread, threadID;
+ var bq, comment, data, o, post, thread, threadID;
if (post = g.posts["" + board + "." + postID]) {
Get.insert(post, root, context);
return;
@@ -1512,37 +1537,49 @@
});
comment = bq.innerHTML.replace(/(^|>)(>[^<$]+)(<|$)/g, '$1$2$3');
threadID = data.thread_num;
- postContainer = Build.post({
+ o = {
postID: postID,
threadID: threadID,
board: board,
- name: data.name,
- capcode: data.capcode,
+ name: data.name_processed,
+ capcode: (function() {
+ switch (data.capcode) {
+ case 'M':
+ return 'mod';
+ case 'A':
+ return 'admin';
+ case 'D':
+ return 'developer';
+ }
+ })(),
tripcode: data.trip,
uniqueID: data.poster_hash,
- email: data.email,
- subject: data.title,
- flag: data.poster_country,
+ email: encodeURIComponent(data.email),
+ subject: data.title_processed,
+ flagCode: data.poster_country,
+ flagName: data.poster_country_name_processed,
date: data.fourchan_date,
dateUTC: data.timestamp,
- comment: comment,
- file: {
- name: data.media_filename,
- origin: data.media_orig,
+ comment: comment
+ };
+ if (data.media_filename) {
+ o.file = {
+ name: data.media_filename_processed,
+ timestamp: data.media_orig,
url: data.media_link || data.remote_media_link,
height: data.media_h,
width: data.media_w,
- isSpoiler: data.spoiler === '1',
MD5: data.media_hash,
size: data.media_size,
turl: data.thumb_link || ("//thumbs.4chan.org/" + board + "/thumb/" + data.preview_orig),
theight: data.preview_h,
- twidth: data.preview_w
- }
- });
+ twidth: data.preview_w,
+ isSpoiler: data.spoiler === '1'
+ };
+ }
board = g.boards[board] || new Board(board);
thread = g.threads["" + board + "." + threadID] || new Thread(threadID, board);
- post = new Post(postContainer, thread, board, {
+ post = new Post(Build.post(o, true), thread, board, {
isArchived: true
});
Main.callbackNodes(Post, [post]);
diff --git a/Cakefile b/Cakefile
index 68a89ad75..a87b4cb0b 100644
--- a/Cakefile
+++ b/Cakefile
@@ -19,11 +19,16 @@ HEADER = """
// @match *://boards.4chan.org/*
// @match *://images.4chan.org/*
// @match *://sys.4chan.org/*
+// @match *://api.4chan.org/*
// @match *://*.foolz.us/api/*
+// @grant GM_getValue
+// @grant GM_setValue
+// @grant GM_deleteValue
+// @grant GM_openInTab
// @run-at document-start
// @updateURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
// @downloadURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
-// @icon http://mayhemydg.github.com/4chan-x/favicon.gif
+// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7
// ==/UserScript==
/* LICENSE
diff --git a/changelog b/changelog
index e17a90f9b..77e861dc3 100644
--- a/changelog
+++ b/changelog
@@ -4,6 +4,22 @@ alpha
Fix Quote Backlinks not affecting inlined quotes.
Fix Quote Highlighting not affecting inlined quotes.
+master
+- Mayhem
+ Use 4chan's API to fetch posts for:
+ - Thread Updater.
+ - Quote Inlining.
+ - Quote Previewing.
+ - Thread Expansion.
+ - Comment Expansion.
+ This will make fetching faster, and reduce bandwidth usage.
+ Add an option to disable 4chan's inline extension. Enabled by default.
+ Fix compatibility with Scriptish's auto-udpater.
+
+2.34.10
+- Mayhem
+ Fix 4chan X. Blame moot.
+
2.34.9
- Mayhem
Add /g/, /k/, /w/, /an/, /cgl/, /ck/, /lit/, /toy/ and /x/ archived image redirection.
diff --git a/latest.js b/latest.js
index 0335ecfb8..d90eff7b7 100644
--- a/latest.js
+++ b/latest.js
@@ -1 +1 @@
-postMessage({version:'2.34.9'},'*')
\ No newline at end of file
+postMessage({version:'2.34.10'},'*')
\ No newline at end of file
diff --git a/script.coffee b/script.coffee
index 430d97430..20b574f44 100644
--- a/script.coffee
+++ b/script.coffee
@@ -1,6 +1,7 @@
Config =
main:
Enhancing:
+ 'Disable 4chan\'s extension': [true, 'Avoid conflicts between 4chan X and 4chan\'s inline extension.']
'404 Redirect': [true, 'Redirect dead threads and images.']
'Keybinds': [true, 'Bind actions to keyboard shortcuts.']
'Time Formatting': [true, 'Localize and format timestamps arbitrarily.']
@@ -741,6 +742,9 @@ Main =
$.id('boardNavDesktopFoot')?.hidden = true
initFeatures: ->
+ if Conf['Disable 4chan\'s extension']
+ localStorage.setItem '4chan-settings', '{"disableAll":true}'
+
if Conf['Resurrect Quotes']
try
Quotify.init()
@@ -935,6 +939,9 @@ body.fourchan_x {
}
/* quote */
+.quotelink.deadlink {
+ text-decoration: underline !important;
+}
.inlined {
opacity: .5;
}
@@ -1060,6 +1067,7 @@ Redirect =
url or ''
Build =
+ spoilerRange: {}
shortFilename: (filename, isReply) ->
# FILENAME SHORTENING SCIENCE:
# OPs have a +10 characters threshold.
@@ -1069,117 +1077,249 @@ Build =
"#{filename[...threshold - 5]}(...).#{filename[-3..]}"
else
filename
- post: (o) ->
+ postFromObject: (data, board) ->
+ o =
+ # id
+ postID: data.no
+ threadID: data.resto or data.no
+ board: board
+ # info
+ name: data.name
+ capcode: data.capcode
+ tripcode: data.trip
+ uniqueID: data.id
+ email: if data.email then encodeURIComponent data.email else ''
+ subject: data.sub
+ flagCode: data.country
+ flagName: data.country_name
+ date: data.now
+ dateUTC: data.time
+ comment: data.com
+ # thread status
+ isSticky: !!data.sticky
+ isClosed: !!data.closed
+ # file
+ if data.ext or data.filedeleted
+ o.file =
+ name: data.filename + data.ext
+ timestamp: "#{data.tim}#{data.ext}"
+ url: "//images.4chan.org/#{board}/src/#{data.tim}#{data.ext}"
+ height: data.h
+ width: data.w
+ MD5: data.md5
+ size: data.fsize
+ turl: "//thumbs.4chan.org/#{board}/thumb/#{data.tim}s.jpg"
+ theight: data.tn_h
+ twidth: data.tn_w
+ isSpoiler: !!data.spoiler
+ isDeleted: !!data.filedeleted
+ Build.post o
+ post: (o, isArchived) ->
+ ###
+ This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS).
+ @license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
+ ###
{
postID, threadID, board
- name, capcode, tripcode, uniqueID, email, subject, flag, flagTitle, date, dateUTC, comment
+ name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC
+ isSticky, isClosed
+ comment
file
} = o
isOP = postID is threadID
- # post info
- html = []
- # input
- html.push " "
- # subject
- html.push "#{subject} "
- # name block
- html.push ""
- # mail start
- html.push "" if email
- # name
- html.push "#{name}"
- # tripcode
- html.push " #{tripcode}" if tripcode
- # user id
- html.push " (ID: #{uniqueID})" if uniqueID
- # capcode
- switch capcode
- when 'M'
- html.push ' ## Mod'
- html.push '
'
- when 'A'
- html.push ' ## Admin'
- html.push '
'
- when 'D'
- html.push ' ## Mod'
- html.push '
'
- # mail end
- html.push '' if email
- # flag
- # XXX what about troll flags in /pol/?
- html.push "
" if flag
- # end name block
- html.push ' '
- # date
- html.push "#{date} "
- # post num
- html.push ''
- html.push "No."
- html.push "'
+ emailEnd = ''
+ else
+ emailStart = ''
+ emailEnd = ''
+
+ subject =
+ if subject
+ "#{subject}"
else
- "/#{board}/res/#{threadID}#q#{postID}"
- }\" title='Quote this post'>#{postID}"
- # XXX closed/sticky?
- html.push ''
- pi = $.el 'div',
- id: "pi#{postID}"
- className: 'postInfo desktop'
- innerHTML: html.join ''
+ ''
- bq = $.el 'blockquote',
- id: "m#{postID}"
- className: 'postMessage'
- innerHTML: comment
+ userID =
+ if !capcode and uniqueID
+ " (ID: " +
+ "#{uniqueID}) "
+ else
+ ''
- # file
- if file.name # XXX need to fix support of Flash
- html = []
- html.push '"
- html.push ""
- html.push "
"
- html.push ''
- fl = $.el 'div',
- id: "f#{postID}"
- className: 'file'
- innerHTML: html.join ''
+ "" +
+ "
" +
+ " "
+ else if file
+ ext = file.name[-3..]
+ if !file.twidth and !file.theight and ext is 'gif' # wtf ?
+ file.twidth = file.width
+ file.theight = file.height
- post = $.el 'div',
- id: "p#{postID}"
- className: "post #{if isOP then 'op' else 'reply'}"
- $.add post, fl if fl and isOP
- $.add post, pi
- $.add post, fl if fl and !isOP
- $.add post, bq
+ fileSize = $.bytesToString file.size
+
+ fileThumb = file.turl
+ if file.isSpoiler
+ fileSize = "Spoiler Image, #{fileSize}"
+ unless isArchived
+ fileThumb = '//static.4chan.org/image/spoiler'
+ if spoilerRange = Build.spoilerRange[board]
+ # Randomize the spoiler image.
+ fileThumb += "-#{board}" + Math.floor 1 + spoilerRange * Math.random()
+ fileThumb += '.png'
+ file.twidth = file.theight = 100
+
+ imgSrc = "" +
+ "
"
+
+ # Ha Ha filenames.
+ # html -> text, translate WebKit's %22s into "s
+ a = $.el 'a', innerHTML: file.name
+ filename = a.textContent.replace /%22/g, '"'
+
+ # shorten filename, get html
+ a.textContent = Build.shortFilename filename
+ shortFilename = a.innerHTML
+
+ # get html
+ a.textContent = filename
+ filename = a.innerHTML.replace /'/g, '''
+
+ fileDims = if ext is 'pdf' then 'PDF' else "#{file.width}x#{file.height}"
+ fileInfo = "File: #{file.timestamp}" +
+ "-(#{fileSize}, #{fileDims}#{
+ if file.isSpoiler
+ ''
+ else
+ ", #{shortFilename}"
+ }" + ")"
+
+ fileHTML = ""
+ else
+ fileHTML = ''
+
+ tripcode =
+ if tripcode
+ " #{tripcode}"
+ else
+ ''
+
+ sticky =
+ if isSticky
+ '
'
+ else
+ ''
+ closed =
+ if isClosed
+ '
'
+ else
+ ''
container = $.el 'div',
id: "pc#{postID}"
className: "postContainer #{if isOP then 'op' else 'reply'}Container"
- unless isOP
- $.add container, $.el 'div',
- id: "sa#{postID}"
- className: 'sideArrows'
- textContent: '>>'
- $.add container, post
+ innerHTML: \
+ (if isOP then '' else ">>
") +
+ "" +
+
+ "
" +
+ "
" +
+ "#{name or ''}" + tripcode +
+ capcodeStart + capcode + userID + flag + sticky + closed +
+ "
#{subject}" +
+ "#{date}" +
+ '
' +
+ "No." +
+ "#{postID}" +
+ '' +
+ '
' +
+
+ (if isOP then fileHTML else '') +
+
+ "
" +
+ "
" +
+ "#{subject} " +
+ "
" +
+ emailStart +
+ "#{name or ''}" + tripcode +
+ capcodeStart + emailEnd + capcode + userID + flag + sticky + closed +
+ ' ' +
+ "
#{date} " +
+ "
" +
+ "No." +
+ "#{postID}" +
+ '' +
+ '
' +
+
+ (if isOP then '' else fileHTML) +
+
+ "
#{comment or ''}
" +
+
+ '
'
+
+ for quote in $$ '.quotelink', container
+ href = quote.getAttribute 'href'
+ continue if href[0] is '/' # Cross-board quote, or board link
+ quote.href = "/#{board}/res/#{href}" # Fix pathnames
container
@@ -1213,7 +1353,7 @@ Get =
root.textContent = "Loading post No.#{postID}..."
if threadID
- $.cache "/#{board}/res/#{threadID}", ->
+ $.cache "//api.4chan.org/#{board}/res/#{threadID}.json", ->
Get.fetchedPost @, board, threadID, postID, root, context
else if url = Redirect.post board, postID
$.cache url, ->
@@ -1242,43 +1382,37 @@ Get =
# The thread can die by the time we check a quote.
if url = Redirect.post board, postID
$.cache url, ->
- Get.archivedPost @, board, postID, root
+ Get.archivedPost @, board, postID, root, context
else
$.addClass root, 'warning'
root.textContent =
if status is 404
- "Thread No.#{threadID} has not been found."
+ "Thread No.#{threadID} 404'd."
else
"Error #{req.status}: #{req.statusText}."
return
- doc = d.implementation.createHTMLDocument ''
- doc.documentElement.innerHTML = req.response
-
- unless pc = doc.getElementById "pc#{postID}"
- # The post can be deleted by the time we check a quote.
- if url = Redirect.post board, postID
- $.cache url, ->
- Get.archivedPost @, board, postID, root
- else
- $.addClass root, 'warning'
- root.textContent = "Post No.#{postID} has not been found."
- return
- pc = d.importNode pc, true
-
- for quote in $$ '.quotelink', pc
- href = quote.getAttribute 'href'
- continue if href[0] is '/' # Cross-board quote, or board link
- quote.href = "/#{board}/res/#{href}" # Fix pathnames
- link = $ 'a[title="Highlight this post"]', pc
- link.href = "/#{board}/res/#{threadID}#p#{postID}"
- link.nextSibling.href = "/#{board}/res/#{threadID}#q#{postID}"
+ posts = JSON.parse(req.response).posts
+ if spoilerRange = posts[0].custom_spoiler
+ Build.spoilerRange[board] = spoilerRange
+ postID = +postID
+ for post in posts
+ break if post.no is postID # we found it!
+ if post.no > postID
+ # The post can be deleted by the time we check a quote.
+ if url = Redirect.post board, postID
+ $.cache url, ->
+ Get.parseArchivedPost @, board, postID, root, context
+ else
+ $.addClass root, 'warning'
+ root.textContent = "Post No.#{postID} was not found."
+ return
board = g.boards[board] or
new Board board
thread = g.threads["#{board}.#{threadID}"] or
new Thread threadID, board
- post = new Post pc, thread, board
+ post = new Post Build.postFromObject(post, board), thread, board
Main.callbackNodes Post, [post]
Get.insert post, root, context
archivedPost: (req, board, postID, root, context) ->
@@ -1332,42 +1466,46 @@ Get =
comment = bq.innerHTML.replace /(^|>)(>[^<$]+)(<|$)/g, '$1$2$3'
threadID = data.thread_num
- postContainer = Build.post
+ o =
# id
- postID: postID
- threadID: threadID
- board: board
+ postID: postID
+ threadID: threadID
+ board: board
# info
- name: data.name
- capcode: data.capcode
- tripcode: data.trip
- uniqueID: data.poster_hash
- email: data.email
- subject: data.title
- flag: data.poster_country
- # XXX flagTitle: data.???
- date: data.fourchan_date
- dateUTC: data.timestamp
- comment: comment
+ name: data.name_processed
+ capcode: switch data.capcode
+ when 'M' then 'mod'
+ when 'A' then 'admin'
+ when 'D' then 'developer'
+ tripcode: data.trip
+ uniqueID: data.poster_hash
+ email: encodeURIComponent data.email
+ subject: data.title_processed
+ flagCode: data.poster_country
+ flagName: data.poster_country_name_processed
+ date: data.fourchan_date
+ dateUTC: data.timestamp
+ comment: comment
# file
- file:
- name: data.media_filename
- origin: data.media_orig
+ if data.media_filename
+ o.file =
+ name: data.media_filename_processed
+ timestamp: data.media_orig
url: data.media_link or data.remote_media_link
height: data.media_h
width: data.media_w
- isSpoiler: data.spoiler is '1'
MD5: data.media_hash
size: data.media_size
turl: data.thumb_link or "//thumbs.4chan.org/#{board}/thumb/#{data.preview_orig}"
theight: data.preview_h
twidth: data.preview_w
+ isSpoiler: data.spoiler is '1'
board = g.boards[board] or
new Board board
thread = g.threads["#{board}.#{threadID}"] or
new Thread threadID, board
- post = new Post postContainer, thread, board,
+ post = new Post Build.post(o, true), thread, board,
isArchived: true
Main.callbackNodes Post, [post]
Get.insert post, root, context