Merge branch 'mayhem' into v3

Conflicts:
	CHANGELOG.md
	css/style.css
	package.json
	src/General/Build.coffee
	src/General/Main.coffee
	src/Images/ImageExpand.coffee
	src/Images/ImageHover.coffee
	src/Monitoring/ThreadUpdater.coffee
This commit is contained in:
Zixaphir 2014-04-12 18:13:21 -07:00
commit 33943a06eb
23 changed files with 1295 additions and 277 deletions

View File

@ -1,3 +1,7 @@
**MayhemYDG**
- Update 4chan namespaces support.
- Better handling of webm playback errors.
### v1.7.8 ### v1.7.8
*2014-04-12* *2014-04-12*

View File

@ -1175,7 +1175,7 @@
Post.prototype.parseQuote = function(quotelink) { Post.prototype.parseQuote = function(quotelink) {
var fullID, match; var fullID, match;
if (!(match = quotelink.href.match(/boards\.4chan\.org\/([^\/]+)\/res\/\d+#p(\d+)$/))) { if (!(match = quotelink.href.match(/boards\.4chan\.org\/([^\/]+)\/thread\/\d+#p(\d+)$/))) {
return; return;
} }
this.nodes.quotelinks.push(quotelink); this.nodes.quotelinks.push(quotelink);
@ -1209,7 +1209,7 @@
size *= 1024; size *= 1024;
} }
this.file.sizeInBytes = size; this.file.sizeInBytes = size;
this.file.thumbURL = that.isArchived ? thumb.src : "" + location.protocol + "//t.4cdn.org/" + this.board + "/thumb/" + (this.file.URL.match(/(\d+)\./)[1]) + "s.jpg"; this.file.thumbURL = that.isArchived ? thumb.src : "" + location.protocol + "//t.4cdn.org/" + this.board + "/" + (this.file.URL.match(/(\d+)\./)[1]) + "s.jpg";
this.file.name = (nameNode = $('span', fileText)) ? nameNode.title || nameNode.textContent : fileText.title; this.file.name = (nameNode = $('span', fileText)) ? nameNode.title || nameNode.textContent : fileText.title;
this.file.isImage = /(jpg|png|gif)$/i.test(this.file.name); this.file.isImage = /(jpg|png|gif)$/i.test(this.file.name);
this.file.isVideo = /webm$/i.test(this.file.name); this.file.isVideo = /webm$/i.test(this.file.name);
@ -1941,23 +1941,6 @@
Polyfill = { Polyfill = {
init: function() {}, init: function() {},
notificationPermission: function() {
if (!window.Notification || 'permission' in Notification || !window.webkitNotifications) {
return;
}
return Object.defineProperty(Notification, 'permission', {
get: function() {
switch (webkitNotifications.checkPermission()) {
case 0:
return 'granted';
case 1:
return 'default';
case 2:
return 'denied';
}
}
});
},
toBlob: function() { toBlob: function() {
var _base; var _base;
return (_base = HTMLCanvasElement.prototype).toBlob || (_base.toBlob = function(cb) { return (_base = HTMLCanvasElement.prototype).toBlob || (_base.toBlob = function(cb) {
@ -1972,26 +1955,6 @@
type: 'image/png' type: 'image/png'
})); }));
}); });
},
visibility: function() {
if ('visibilityState' in d) {
return;
}
Object.defineProperties(HTMLDocument.prototype, {
visibilityState: {
get: function() {
return this.webkitVisibilityState;
}
},
hidden: {
get: function() {
return this.webkitHidden;
}
}
});
return $.on(d, 'webkitvisibilitychange', function() {
return $.event('visibilitychange');
});
} }
}; };
@ -3694,12 +3657,12 @@
o.file = { o.file = {
name: data.filename + data.ext, name: data.filename + data.ext,
timestamp: "" + data.tim + data.ext, timestamp: "" + data.tim + data.ext,
url: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/src/" + data.filename + data.ext : "//i.4cdn.org/" + boardID + "/src/" + data.tim + data.ext, url: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/" + data.filename + data.ext : "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext,
height: data.h, height: data.h,
width: data.w, width: data.w,
MD5: data.md5, MD5: data.md5,
size: data.fsize, size: data.fsize,
turl: "//" + (Build.thumbRotate()) + ".t.4cdn.org/" + boardID + "/thumb/" + data.tim + "s.jpg", turl: "//" + (Build.thumbRotate()) + ".t.4cdn.org/" + boardID + "/" + data.tim + "s.jpg",
theight: data.tn_h, theight: data.tn_h,
twidth: data.tn_w, twidth: data.tn_w,
isSpoiler: !!data.spoiler, isSpoiler: !!data.spoiler,
@ -3786,14 +3749,14 @@
if (isOP && g.VIEW === 'index') { if (isOP && g.VIEW === 'index') {
pageNum = Math.floor(Index.liveThreadData.keys.indexOf("" + postID) / Index.threadsNumPerPage); pageNum = Math.floor(Index.liveThreadData.keys.indexOf("" + postID) / Index.threadsNumPerPage);
pageIcon = " <span class=page-num title='This thread is on page " + pageNum + " in the original index.'>Page " + pageNum + "</span>"; pageIcon = " <span class=page-num title='This thread is on page " + pageNum + " in the original index.'>Page " + pageNum + "</span>";
replyLink = " &nbsp; <span>[<a href='/" + boardID + "/res/" + threadID + "' class=replylink>Reply</a>]</span>"; replyLink = " &nbsp; <span>[<a href='/" + boardID + "/thread/" + threadID + "' class=replylink>Reply</a>]</span>";
} else { } else {
pageIcon = replyLink = ''; pageIcon = replyLink = '';
} }
container = $.el('div', { container = $.el('div', {
id: "pc" + postID, id: "pc" + postID,
className: "postContainer " + (isOP ? 'op' : 'reply') + "Container", className: "postContainer " + (isOP ? 'op' : 'reply') + "Container",
innerHTML: "" + (isOP ? '' : "<div class=sideArrows>&gt;&gt;</div>") + "<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcodeIcon === 'admin_highlight' ? ' highlightPost' : '') + "'>" + (isOP ? fileHTML : '') + "<div class='postInfo'><input type=checkbox name=" + postID + " value=delete>" + ' ' + "<span class=subject>" + (subject || '') + "</span>" + ' ' + "<span class='nameBlock" + capcodeClass + "'>" + emailStart + "<span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + emailEnd + capcodeIcon + userID + flag) + "</span>" + " " + "<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span>" + ' ' + "<span class='postNum'><a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a>" + (pageIcon + sticky + closed + replyLink) + "</span></div>" + (isOP ? '' : fileHTML) + "<blockquote class=postMessage>" + (comment || '') + "</blockquote>" + ' ' + "</div>" innerHTML: (isOP ? '' : "<div class=sideArrows>&gt;&gt;</div>") + ("<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : '') + "'>") + (isOP ? fileHTML : '') + "<div class=postInfo>" + ("<input type=checkbox name=" + postID + " value=delete> ") + ("<span class=subject>" + (subject || '') + "</span> ") + ("<span class='nameBlock" + capcodeClass + "'>") + emailStart + ("<span class=name>" + (name || '') + "</span>") + tripcode + capcodeStart + emailEnd + capcodeIcon + userID + flag + ' </span> ' + ("<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span> ") + "<span class='postNum'>" + ("<a href=" + ("/" + boardID + "/thread/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a>") + ("<a href='" + (g.VIEW === 'thread' && g.THREADID === threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/thread/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a>") + pageIcon + sticky + closed + replyLink + '</span>' + '</div>' + (isOP ? '' : fileHTML) + ("<blockquote class=postMessage>" + (comment || '') + "</blockquote> ") + '</div>'
}); });
_ref = $$('.quotelink', container); _ref = $$('.quotelink', container);
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@ -3802,7 +3765,7 @@
if (href[0] === '/') { if (href[0] === '/') {
continue; continue;
} }
quote.href = "/" + boardID + "/res/" + href; quote.href = "/" + boardID + "/thread/" + href;
} }
return container; return container;
}, },
@ -3817,7 +3780,7 @@
return $.el('a', { return $.el('a', {
className: 'summary', className: 'summary',
textContent: text.join(' '), textContent: text.join(' '),
href: "/" + boardID + "/res/" + threadID href: "/" + boardID + "/thread/" + threadID
}); });
}, },
thread: function(board, data, full) { thread: function(board, data, full) {
@ -3858,7 +3821,7 @@
comment = thread.OP.nodes.comment.innerHTML.replace(/(<br>\s*){2,}/g, '<br>'); comment = thread.OP.nodes.comment.innerHTML.replace(/(<br>\s*){2,}/g, '<br>');
root = $.el('div', { root = $.el('div', {
className: 'catalog-thread', className: 'catalog-thread',
innerHTML: "<a href=\"/" + thread.board + "/res/" + thread.ID + "\" class=\"thumb\"></a><div class=\"thread-stats\" title=\"Post count / File count / Page count\"><span class=\"post-count\">" + postCount + "</span> / <span class=\"file-count\">" + fileCount + "</span> / <span class=\"page-count\">" + pageCount + "</span><span class=\"thread-icons\"></span></div>" + subject + "<div class=\"comment\">" + comment + "</div>" innerHTML: "<a href=\"/" + thread.board + "/thread/" + thread.ID + "\" class=\"thumb\"></a><div class=\"thread-stats\" title=\"Post count / File count / Page count\"><span class=\"post-count\">" + postCount + "</span> / <span class=\"file-count\">" + fileCount + "</span> / <span class=\"page-count\">" + pageCount + "</span><span class=\"thread-icons\"></span></div>" + subject + "<div class=\"comment\">" + comment + "</div>"
}); });
root.dataset.fullID = thread.fullID; root.dataset.fullID = thread.fullID;
if (thread.isPinned) { if (thread.isPinned) {
@ -4016,7 +3979,7 @@
} }
root.textContent = "Loading post No." + postID + "..."; root.textContent = "Loading post No." + postID + "...";
if (threadID) { if (threadID) {
return $.cache("//a.4cdn.org/" + boardID + "/res/" + threadID + ".json", function() { return $.cache("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", function() {
return Get.fetchedPost(this, boardID, threadID, postID, root, context); return Get.fetchedPost(this, boardID, threadID, postID, root, context);
}); });
} else if (url = Redirect.to('post', { } else if (url = Redirect.to('post', {
@ -4150,7 +4113,7 @@
width: data.media.media_w, width: data.media.media_w,
MD5: data.media.media_hash, MD5: data.media.media_hash,
size: data.media.media_size, size: data.media.media_size,
turl: data.media.thumb_link || ("//t.4cdn.org/" + boardID + "/thumb/" + data.media.preview_orig), turl: data.media.thumb_link || ("//t.4cdn.org/" + boardID + "/" + data.media.preview_orig),
theight: data.media.preview_h, theight: data.media.preview_h,
twidth: data.media.preview_w, twidth: data.media.preview_w,
isSpoiler: data.media.spoiler === '1' isSpoiler: data.media.spoiler === '1'
@ -4933,27 +4896,10 @@
className: 'show-post-button fa fa-plus-square-o', className: 'show-post-button fa fa-plus-square-o',
href: 'javascript:;' href: 'javascript:;'
}); });
Post.callbacks.push({ return Post.callbacks.push({
name: 'Post Hiding', name: 'Post Hiding',
cb: this.node cb: this.node
}); });
return $.get('hiddenThreads', null, function(_arg) {
var board, boardID, hiddenThreads, threadID, val, _base, _base1, _ref;
hiddenThreads = _arg.hiddenThreads;
if (!hiddenThreads) {
return;
}
_ref = hiddenThreads.boards;
for (boardID in _ref) {
board = _ref[boardID];
for (threadID in board) {
val = board[threadID];
((_base = ((_base1 = PostHiding.db.data.boards)[boardID] || (_base1[boardID] = {})))[threadID] || (_base[threadID] = {}))[threadID] = val;
}
}
PostHiding.db.save();
return $["delete"]('hiddenThreads');
});
}, },
node: function() { node: function() {
var a, data, label; var a, data, label;
@ -5354,7 +5300,7 @@
var a, frag, hash, text; var a, frag, hash, text;
frag = QuoteBacklink.frag.cloneNode(true); frag = QuoteBacklink.frag.cloneNode(true);
a = frag.lastElementChild; a = frag.lastElementChild;
a.href = "/" + quoter.board + "/res/" + quoter.thread + "#p" + quoter; a.href = "/" + quoter.board + "/thread/" + quoter.thread + "#p" + quoter;
a.textContent = text = QuoteBacklink.funk(quoter.ID); a.textContent = text = QuoteBacklink.funk(quoter.ID);
if (quoter.isDead) { if (quoter.isDead) {
$.addClass(a, 'deadlink'); $.addClass(a, 'deadlink');
@ -5926,7 +5872,7 @@
quoteID = "" + boardID + "." + postID; quoteID = "" + boardID + "." + postID;
if (post = g.posts[quoteID]) { if (post = g.posts[quoteID]) {
a = $.el('a', { a = $.el('a', {
href: "/" + boardID + "/res/" + post.thread + "#p" + postID, href: "/" + boardID + "/thread/" + post.thread + "#p" + postID,
className: post.isDead ? 'quotelink deadlink' : 'quotelink', className: post.isDead ? 'quotelink deadlink' : 'quotelink',
textContent: quote textContent: quote
}); });
@ -6254,7 +6200,9 @@
_ref = $$('br', frag); _ref = $$('br', frag);
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i]; node = _ref[_i];
$.replace(node, $.tn('\n>')); if (node !== frag.lastElementChild) {
$.replace(node, $.tn('\n>'));
}
} }
_ref1 = $$('s', frag); _ref1 = $$('s', frag);
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
@ -6866,7 +6814,7 @@
isReply: isReply, isReply: isReply,
threadID: threadID threadID: threadID
}); });
URL = threadID === postID ? "/" + g.BOARD + "/res/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "/" + g.BOARD + "/res/" + threadID + "#p" + postID : void 0; URL = threadID === postID ? "/" + g.BOARD + "/thread/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0;
if (URL) { if (URL) {
if (Conf['Open Post in New Tab']) { if (Conf['Open Post in New Tab']) {
$.open(URL); $.open(URL);
@ -8092,8 +8040,12 @@
delete post.file.videoControls; delete post.file.videoControls;
} }
$.rmClass(post.nodes.root, 'expanded-image'); $.rmClass(post.nodes.root, 'expanded-image');
$.rmClass(thumb, 'expanding'); $.rmClass(post.file.thumb, 'expanding');
return post.file.isExpanded = false; delete post.file.isExpanding;
delete post.file.isExpanded;
if (post.file.isVideo && post.file.fullImage) {
return post.file.fullImage.pause();
}
}, },
expand: function(post, src, disableAutoplay) { expand: function(post, src, disableAutoplay) {
var el, isVideo, thumb, _ref; var el, isVideo, thumb, _ref;
@ -8209,19 +8161,40 @@
return $.add(file.text, file.videoControls); return $.add(file.text, file.videoControls);
}, },
error: function() { error: function() {
var URL, post, src, timeoutID; var URL, error, post, src, timeoutID;
post = Get.postFromNode(this); post = Get.postFromNode(this);
$.rm(this); $.rm(this);
delete post.file.isReady;
delete post.file.fullImage; delete post.file.fullImage;
if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) { if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) {
return; return;
} }
ImageExpand.contract(post); ImageExpand.contract(post);
if (this.error && this.error.code !== this.error.MEDIA_ERR_NETWORK) {
error = (function() {
switch (this.error.code) {
case 1:
return 'MEDIA_ERR_ABORTED';
case 3:
return 'MEDIA_ERR_DECODE';
case 4:
return 'MEDIA_ERR_SRC_NOT_SUPPORTED';
case 5:
return 'MEDIA_ERR_ENCRYPTED';
}
}).call(this);
post.file.error = $.el('div', {
textContent: "Playback error: " + error,
className: 'warning'
});
$.after(post.file.thumb, post.file.error);
return;
}
src = this.src.split('/'); src = this.src.split('/');
if (src[2] === 'i.4cdn.org') { if (src[2] === 'i.4cdn.org') {
URL = Redirect.to('file', { URL = Redirect.to('file', {
boardID: src[3], boardID: src[3],
filename: src[5] filename: src[4].replace(/\?.+$/, '')
}); });
if (URL) { if (URL) {
setTimeout(ImageExpand.expand, 10000, post, URL); setTimeout(ImageExpand.expand, 10000, post, URL);
@ -8232,7 +8205,7 @@
} }
} }
timeoutID = setTimeout(ImageExpand.expand, 10000, post); timeoutID = setTimeout(ImageExpand.expand, 10000, post);
return $.ajax("//a.4cdn.org/" + post.board + "/res/" + post.thread + ".json", { return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", {
onload: function() { onload: function() {
var postObj, _i, _len, _ref; var postObj, _i, _len, _ref;
if (this.status !== 200) { if (this.status !== 200) {
@ -8367,10 +8340,11 @@
el: el, el: el,
latestEvent: e, latestEvent: e,
endEvents: 'mouseout click', endEvents: 'mouseout click',
asapTest: function() { asapTest: post.file.isImage ? function() {
return el.videoHeight || el.naturalHeight; return el.naturalHeight;
} : function() {
return el.readyState >= el.HAVE_CURRENT_DATA;
}, },
noRemove: true,
cb: function() { cb: function() {
if (isVideo) { if (isVideo) {
el.pause(); el.pause();
@ -8391,7 +8365,7 @@
if (src[2] === 'i.4cdn.org') { if (src[2] === 'i.4cdn.org') {
URL = Redirect.to('file', { URL = Redirect.to('file', {
boardID: src[3], boardID: src[3],
filename: src[5].replace(/\?.+$/, '') filename: src[4].replace(/\?.+$/, '')
}); });
if (URL) { if (URL) {
this.src = URL; this.src = URL;
@ -8406,7 +8380,7 @@
return _this.src = post.file.URL + '?' + Date.now(); return _this.src = post.file.URL + '?' + Date.now();
}; };
})(this)), 3000); })(this)), 3000);
return $.ajax("//a.4cdn.org/" + post.board + "/res/" + post.thread + ".json", { return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", {
onload: function() { onload: function() {
var postObj, _i, _len, _ref; var postObj, _i, _len, _ref;
if (this.status !== 200) { if (this.status !== 200) {
@ -10007,9 +9981,12 @@
if ((_ref = ThreadUpdater.req) != null) { if ((_ref = ThreadUpdater.req) != null) {
_ref.abort(); _ref.abort();
} }
url = "//a.4cdn.org/" + ThreadUpdater.thread.board + "/res/" + ThreadUpdater.thread + ".json"; url = "//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json";
return ThreadUpdater.req = $.ajax(url, { return ThreadUpdater.req = $.ajax(url, {
onloadend: ThreadUpdater.cb.load onabort: ThreadUpdater.cb.load,
onloadend: ThreadUpdater.cb.load,
ontimeout: ThreadUpdater.cb.load,
timeout: $.MINUTE
}, { }, {
whenModified: true whenModified: true
}); });
@ -10317,7 +10294,7 @@
} }
fetchCount = ThreadWatcher.fetchCount; fetchCount = ThreadWatcher.fetchCount;
fetchCount.fetching++; fetchCount.fetching++;
return $.ajax("//a.4cdn.org/" + boardID + "/res/" + threadID + ".json", { return $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", {
onloadend: function() { onloadend: function() {
var status; var status;
fetchCount.fetched++; fetchCount.fetched++;
@ -10385,7 +10362,7 @@
}); });
} }
link = $.el('a', { link = $.el('a', {
href: href || ("/" + boardID + "/res/" + threadID), href: href || ("/" + boardID + "/thread/" + threadID),
textContent: data.excerpt, textContent: data.excerpt,
title: data.excerpt title: data.excerpt
}); });
@ -11541,7 +11518,7 @@
var status; var status;
ExpandThread.statuses[thread] = status = {}; ExpandThread.statuses[thread] = status = {};
a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g)))); a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g))));
return status.req = $.cache("//a.4cdn.org/" + thread.board + "/res/" + thread + ".json", function() { return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() {
delete status.req; delete status.req;
return ExpandThread.parse(this, thread, a); return ExpandThread.parse(this, thread, a);
}); });
@ -12192,7 +12169,7 @@
if (g.VIEW !== 'index') { if (g.VIEW !== 'index') {
return; return;
} }
url = "/" + thread.board + "/res/" + thread; url = "/" + thread.board + "/thread/" + thread;
if (tab) { if (tab) {
return $.open(url); return $.open(url);
} else { } else {
@ -13491,7 +13468,7 @@
data = { data = {
isReply: true, isReply: true,
file: { file: {
URL: '//i.4cdn.org/g/src/1334437723720.jpg', URL: '//i.4cdn.org/g/1334437723720.jpg',
name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg', name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg',
size: '276 KB', size: '276 KB',
sizeInBytes: 276 * 1024, sizeInBytes: 276 * 1024,

View File

@ -16,7 +16,7 @@
}], }],
"homepage_url": "https://github.com/ccd0/4chan-x", "homepage_url": "https://github.com/ccd0/4chan-x",
"update_url": "https://ccd0.github.io/4chan-x/builds/updates.xml", "update_url": "https://ccd0.github.io/4chan-x/builds/updates.xml",
"minimum_chrome_version": "32", "minimum_chrome_version": "33",
"permissions": [ "permissions": [
"storage", "storage",
"http://*/", "http://*/",

View File

@ -1231,7 +1231,7 @@
Post.prototype.parseQuote = function(quotelink) { Post.prototype.parseQuote = function(quotelink) {
var fullID, match; var fullID, match;
if (!(match = quotelink.href.match(/boards\.4chan\.org\/([^\/]+)\/res\/\d+#p(\d+)$/))) { if (!(match = quotelink.href.match(/boards\.4chan\.org\/([^\/]+)\/thread\/\d+#p(\d+)$/))) {
return; return;
} }
this.nodes.quotelinks.push(quotelink); this.nodes.quotelinks.push(quotelink);
@ -1265,7 +1265,7 @@
size *= 1024; size *= 1024;
} }
this.file.sizeInBytes = size; this.file.sizeInBytes = size;
this.file.thumbURL = that.isArchived ? thumb.src : "" + location.protocol + "//t.4cdn.org/" + this.board + "/thumb/" + (this.file.URL.match(/(\d+)\./)[1]) + "s.jpg"; this.file.thumbURL = that.isArchived ? thumb.src : "" + location.protocol + "//t.4cdn.org/" + this.board + "/" + (this.file.URL.match(/(\d+)\./)[1]) + "s.jpg";
this.file.name = (nameNode = $('span', fileText)) ? nameNode.title || nameNode.textContent : fileText.title; this.file.name = (nameNode = $('span', fileText)) ? nameNode.title || nameNode.textContent : fileText.title;
this.file.name = this.file.name.replace(/%22/g, '"'); this.file.name = this.file.name.replace(/%22/g, '"');
this.file.isImage = /(jpg|png|gif)$/i.test(this.file.name); this.file.isImage = /(jpg|png|gif)$/i.test(this.file.name);
@ -1998,26 +1998,7 @@
Polyfill = { Polyfill = {
init: function() { init: function() {
this.notificationPermission(); return this.toBlob();
this.toBlob();
return this.visibility();
},
notificationPermission: function() {
if (!window.Notification || 'permission' in Notification || !window.webkitNotifications) {
return;
}
return Object.defineProperty(Notification, 'permission', {
get: function() {
switch (webkitNotifications.checkPermission()) {
case 0:
return 'granted';
case 1:
return 'default';
case 2:
return 'denied';
}
}
});
}, },
toBlob: function() { toBlob: function() {
var _base; var _base;
@ -2033,26 +2014,6 @@
type: 'image/png' type: 'image/png'
})); }));
}); });
},
visibility: function() {
if ('visibilityState' in d) {
return;
}
Object.defineProperties(HTMLDocument.prototype, {
visibilityState: {
get: function() {
return this.webkitVisibilityState;
}
},
hidden: {
get: function() {
return this.webkitHidden;
}
}
});
return $.on(d, 'webkitvisibilitychange', function() {
return $.event('visibilitychange');
});
} }
}; };
@ -3755,12 +3716,12 @@
o.file = { o.file = {
name: data.filename + data.ext, name: data.filename + data.ext,
timestamp: "" + data.tim + data.ext, timestamp: "" + data.tim + data.ext,
url: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/src/" + data.filename + data.ext : "//i.4cdn.org/" + boardID + "/src/" + data.tim + data.ext, url: boardID === 'f' ? "//i.4cdn.org/" + boardID + "/" + data.filename + data.ext : "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext,
height: data.h, height: data.h,
width: data.w, width: data.w,
MD5: data.md5, MD5: data.md5,
size: data.fsize, size: data.fsize,
turl: "//" + (Build.thumbRotate()) + ".t.4cdn.org/" + boardID + "/thumb/" + data.tim + "s.jpg", turl: "//" + (Build.thumbRotate()) + ".t.4cdn.org/" + boardID + "/" + data.tim + "s.jpg",
theight: data.tn_h, theight: data.tn_h,
twidth: data.tn_w, twidth: data.tn_w,
isSpoiler: !!data.spoiler, isSpoiler: !!data.spoiler,
@ -3847,14 +3808,14 @@
if (isOP && g.VIEW === 'index') { if (isOP && g.VIEW === 'index') {
pageNum = Math.floor(Index.liveThreadData.keys.indexOf("" + postID) / Index.threadsNumPerPage); pageNum = Math.floor(Index.liveThreadData.keys.indexOf("" + postID) / Index.threadsNumPerPage);
pageIcon = " <span class=page-num title='This thread is on page " + pageNum + " in the original index.'>Page " + pageNum + "</span>"; pageIcon = " <span class=page-num title='This thread is on page " + pageNum + " in the original index.'>Page " + pageNum + "</span>";
replyLink = " &nbsp; <span>[<a href='/" + boardID + "/res/" + threadID + "' class=replylink>Reply</a>]</span>"; replyLink = " &nbsp; <span>[<a href='/" + boardID + "/thread/" + threadID + "' class=replylink>Reply</a>]</span>";
} else { } else {
pageIcon = replyLink = ''; pageIcon = replyLink = '';
} }
container = $.el('div', { container = $.el('div', {
id: "pc" + postID, id: "pc" + postID,
className: "postContainer " + (isOP ? 'op' : 'reply') + "Container", className: "postContainer " + (isOP ? 'op' : 'reply') + "Container",
innerHTML: "" + (isOP ? '' : "<div class=sideArrows>&gt;&gt;</div>") + "<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcodeIcon === 'admin_highlight' ? ' highlightPost' : '') + "'>" + (isOP ? fileHTML : '') + "<div class='postInfo'><input type=checkbox name=" + postID + " value=delete>" + ' ' + "<span class=subject>" + (subject || '') + "</span>" + ' ' + "<span class='nameBlock" + capcodeClass + "'>" + emailStart + "<span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + emailEnd + capcodeIcon + userID + flag) + "</span>" + " " + "<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span>" + ' ' + "<span class='postNum'><a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a>" + (pageIcon + sticky + closed + replyLink) + "</span></div>" + (isOP ? '' : fileHTML) + "<blockquote class=postMessage>" + (comment || '') + "</blockquote>" + ' ' + "</div>" innerHTML: (isOP ? '' : "<div class=sideArrows>&gt;&gt;</div>") + ("<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : '') + "'>") + (isOP ? fileHTML : '') + "<div class=postInfo>" + ("<input type=checkbox name=" + postID + " value=delete> ") + ("<span class=subject>" + (subject || '') + "</span> ") + ("<span class='nameBlock" + capcodeClass + "'>") + emailStart + ("<span class=name>" + (name || '') + "</span>") + tripcode + capcodeStart + emailEnd + capcodeIcon + userID + flag + ' </span> ' + ("<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span> ") + "<span class='postNum'>" + ("<a href=" + ("/" + boardID + "/thread/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a>") + ("<a href='" + (g.VIEW === 'thread' && g.THREADID === threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/thread/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a>") + pageIcon + sticky + closed + replyLink + '</span>' + '</div>' + (isOP ? '' : fileHTML) + ("<blockquote class=postMessage>" + (comment || '') + "</blockquote> ") + '</div>'
}); });
_ref = $$('.quotelink', container); _ref = $$('.quotelink', container);
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@ -3863,7 +3824,7 @@
if (href[0] === '/') { if (href[0] === '/') {
continue; continue;
} }
quote.href = "/" + boardID + "/res/" + href; quote.href = "/" + boardID + "/thread/" + href;
} }
return container; return container;
}, },
@ -3878,7 +3839,7 @@
return $.el('a', { return $.el('a', {
className: 'summary', className: 'summary',
textContent: text.join(' '), textContent: text.join(' '),
href: "/" + boardID + "/res/" + threadID href: "/" + boardID + "/thread/" + threadID
}); });
}, },
thread: function(board, data, full) { thread: function(board, data, full) {
@ -3919,7 +3880,7 @@
comment = thread.OP.nodes.comment.innerHTML.replace(/(<br>\s*){2,}/g, '<br>'); comment = thread.OP.nodes.comment.innerHTML.replace(/(<br>\s*){2,}/g, '<br>');
root = $.el('div', { root = $.el('div', {
className: 'catalog-thread', className: 'catalog-thread',
innerHTML: "<a href=\"/" + thread.board + "/res/" + thread.ID + "\" class=\"thumb\"></a><div class=\"thread-stats\" title=\"Post count / File count / Page count\"><span class=\"post-count\">" + postCount + "</span> / <span class=\"file-count\">" + fileCount + "</span> / <span class=\"page-count\">" + pageCount + "</span><span class=\"thread-icons\"></span></div>" + subject + "<div class=\"comment\">" + comment + "</div>" innerHTML: "<a href=\"/" + thread.board + "/thread/" + thread.ID + "\" class=\"thumb\"></a><div class=\"thread-stats\" title=\"Post count / File count / Page count\"><span class=\"post-count\">" + postCount + "</span> / <span class=\"file-count\">" + fileCount + "</span> / <span class=\"page-count\">" + pageCount + "</span><span class=\"thread-icons\"></span></div>" + subject + "<div class=\"comment\">" + comment + "</div>"
}); });
root.dataset.fullID = thread.fullID; root.dataset.fullID = thread.fullID;
if (thread.isPinned) { if (thread.isPinned) {
@ -4077,7 +4038,7 @@
} }
root.textContent = "Loading post No." + postID + "..."; root.textContent = "Loading post No." + postID + "...";
if (threadID) { if (threadID) {
return $.cache("//a.4cdn.org/" + boardID + "/res/" + threadID + ".json", function() { return $.cache("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", function() {
return Get.fetchedPost(this, boardID, threadID, postID, root, context); return Get.fetchedPost(this, boardID, threadID, postID, root, context);
}); });
} else if (url = Redirect.to('post', { } else if (url = Redirect.to('post', {
@ -4211,7 +4172,7 @@
width: data.media.media_w, width: data.media.media_w,
MD5: data.media.media_hash, MD5: data.media.media_hash,
size: data.media.media_size, size: data.media.media_size,
turl: data.media.thumb_link || ("//t.4cdn.org/" + boardID + "/thumb/" + data.media.preview_orig), turl: data.media.thumb_link || ("//t.4cdn.org/" + boardID + "/" + data.media.preview_orig),
theight: data.media.preview_h, theight: data.media.preview_h,
twidth: data.media.preview_w, twidth: data.media.preview_w,
isSpoiler: data.media.spoiler === '1' isSpoiler: data.media.spoiler === '1'
@ -4987,27 +4948,10 @@
className: 'show-post-button fa fa-plus-square-o', className: 'show-post-button fa fa-plus-square-o',
href: 'javascript:;' href: 'javascript:;'
}); });
Post.callbacks.push({ return Post.callbacks.push({
name: 'Post Hiding', name: 'Post Hiding',
cb: this.node cb: this.node
}); });
return $.get('hiddenThreads', null, function(_arg) {
var board, boardID, hiddenThreads, threadID, val, _base, _base1, _ref;
hiddenThreads = _arg.hiddenThreads;
if (!hiddenThreads) {
return;
}
_ref = hiddenThreads.boards;
for (boardID in _ref) {
board = _ref[boardID];
for (threadID in board) {
val = board[threadID];
((_base = ((_base1 = PostHiding.db.data.boards)[boardID] || (_base1[boardID] = {})))[threadID] || (_base[threadID] = {}))[threadID] = val;
}
}
PostHiding.db.save();
return $["delete"]('hiddenThreads');
});
}, },
node: function() { node: function() {
var a, data, label; var a, data, label;
@ -5408,7 +5352,7 @@
var a, frag, hash, text; var a, frag, hash, text;
frag = QuoteBacklink.frag.cloneNode(true); frag = QuoteBacklink.frag.cloneNode(true);
a = frag.lastElementChild; a = frag.lastElementChild;
a.href = "/" + quoter.board + "/res/" + quoter.thread + "#p" + quoter; a.href = "/" + quoter.board + "/thread/" + quoter.thread + "#p" + quoter;
a.textContent = text = QuoteBacklink.funk(quoter.ID); a.textContent = text = QuoteBacklink.funk(quoter.ID);
if (quoter.isDead) { if (quoter.isDead) {
$.addClass(a, 'deadlink'); $.addClass(a, 'deadlink');
@ -5980,7 +5924,7 @@
quoteID = "" + boardID + "." + postID; quoteID = "" + boardID + "." + postID;
if (post = g.posts[quoteID]) { if (post = g.posts[quoteID]) {
a = $.el('a', { a = $.el('a', {
href: "/" + boardID + "/res/" + post.thread + "#p" + postID, href: "/" + boardID + "/thread/" + post.thread + "#p" + postID,
className: post.isDead ? 'quotelink deadlink' : 'quotelink', className: post.isDead ? 'quotelink deadlink' : 'quotelink',
textContent: quote textContent: quote
}); });
@ -6318,7 +6262,9 @@
_ref = $$('br', frag); _ref = $$('br', frag);
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i]; node = _ref[_i];
$.replace(node, $.tn('\n>')); if (node !== frag.lastElementChild) {
$.replace(node, $.tn('\n>'));
}
} }
_ref1 = $$('s', frag); _ref1 = $$('s', frag);
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
@ -6911,7 +6857,7 @@
isReply: isReply, isReply: isReply,
threadID: threadID threadID: threadID
}); });
URL = threadID === postID ? "/" + g.BOARD + "/res/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "/" + g.BOARD + "/res/" + threadID + "#p" + postID : void 0; URL = threadID === postID ? "/" + g.BOARD + "/thread/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0;
if (URL) { if (URL) {
if (Conf['Open Post in New Tab']) { if (Conf['Open Post in New Tab']) {
$.open(URL); $.open(URL);
@ -8131,8 +8077,12 @@
delete post.file.videoControls; delete post.file.videoControls;
} }
$.rmClass(post.nodes.root, 'expanded-image'); $.rmClass(post.nodes.root, 'expanded-image');
$.rmClass(thumb, 'expanding'); $.rmClass(post.file.thumb, 'expanding');
return post.file.isExpanded = false; delete post.file.isExpanding;
delete post.file.isExpanded;
if (post.file.isVideo && post.file.fullImage) {
return post.file.fullImage.pause();
}
}, },
expand: function(post, src, disableAutoplay) { expand: function(post, src, disableAutoplay) {
var el, isVideo, thumb, _ref; var el, isVideo, thumb, _ref;
@ -8248,19 +8198,40 @@
return $.add(file.text, file.videoControls); return $.add(file.text, file.videoControls);
}, },
error: function() { error: function() {
var URL, post, src, timeoutID; var URL, error, post, src, timeoutID;
post = Get.postFromNode(this); post = Get.postFromNode(this);
$.rm(this); $.rm(this);
delete post.file.isReady;
delete post.file.fullImage; delete post.file.fullImage;
if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) { if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) {
return; return;
} }
ImageExpand.contract(post); ImageExpand.contract(post);
if (this.error && this.error.code !== this.error.MEDIA_ERR_NETWORK) {
error = (function() {
switch (this.error.code) {
case 1:
return 'MEDIA_ERR_ABORTED';
case 3:
return 'MEDIA_ERR_DECODE';
case 4:
return 'MEDIA_ERR_SRC_NOT_SUPPORTED';
case 5:
return 'MEDIA_ERR_ENCRYPTED';
}
}).call(this);
post.file.error = $.el('div', {
textContent: "Playback error: " + error,
className: 'warning'
});
$.after(post.file.thumb, post.file.error);
return;
}
src = this.src.split('/'); src = this.src.split('/');
if (src[2] === 'i.4cdn.org') { if (src[2] === 'i.4cdn.org') {
URL = Redirect.to('file', { URL = Redirect.to('file', {
boardID: src[3], boardID: src[3],
filename: src[5] filename: src[4].replace(/\?.+$/, '')
}); });
if (URL) { if (URL) {
setTimeout(ImageExpand.expand, 10000, post, URL); setTimeout(ImageExpand.expand, 10000, post, URL);
@ -8395,10 +8366,11 @@
el: el, el: el,
latestEvent: e, latestEvent: e,
endEvents: 'mouseout click', endEvents: 'mouseout click',
asapTest: function() { asapTest: post.file.isImage ? function() {
return el.videoHeight || el.naturalHeight; return el.naturalHeight;
} : function() {
return el.readyState >= el.HAVE_CURRENT_DATA;
}, },
noRemove: true,
cb: function() { cb: function() {
if (isVideo) { if (isVideo) {
el.pause(); el.pause();
@ -8419,7 +8391,7 @@
if (src[2] === 'i.4cdn.org') { if (src[2] === 'i.4cdn.org') {
URL = Redirect.to('file', { URL = Redirect.to('file', {
boardID: src[3], boardID: src[3],
filename: src[5].replace(/\?.+$/, '') filename: src[4].replace(/\?.+$/, '')
}); });
if (URL) { if (URL) {
this.src = URL; this.src = URL;
@ -10024,9 +9996,12 @@
if ((_ref = ThreadUpdater.req) != null) { if ((_ref = ThreadUpdater.req) != null) {
_ref.abort(); _ref.abort();
} }
url = "//a.4cdn.org/" + ThreadUpdater.thread.board + "/res/" + ThreadUpdater.thread + ".json"; url = "//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json";
return ThreadUpdater.req = $.ajax(url, { return ThreadUpdater.req = $.ajax(url, {
onloadend: ThreadUpdater.cb.load onabort: ThreadUpdater.cb.load,
onloadend: ThreadUpdater.cb.load,
ontimeout: ThreadUpdater.cb.load,
timeout: $.MINUTE
}, { }, {
whenModified: true whenModified: true
}); });
@ -10334,7 +10309,7 @@
} }
fetchCount = ThreadWatcher.fetchCount; fetchCount = ThreadWatcher.fetchCount;
fetchCount.fetching++; fetchCount.fetching++;
return $.ajax("//a.4cdn.org/" + boardID + "/res/" + threadID + ".json", { return $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", {
onloadend: function() { onloadend: function() {
var status; var status;
fetchCount.fetched++; fetchCount.fetched++;
@ -10402,7 +10377,7 @@
}); });
} }
link = $.el('a', { link = $.el('a', {
href: href || ("/" + boardID + "/res/" + threadID), href: href || ("/" + boardID + "/thread/" + threadID),
textContent: data.excerpt, textContent: data.excerpt,
title: data.excerpt title: data.excerpt
}); });
@ -11557,7 +11532,7 @@
var status; var status;
ExpandThread.statuses[thread] = status = {}; ExpandThread.statuses[thread] = status = {};
a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g)))); a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g))));
return status.req = $.cache("//a.4cdn.org/" + thread.board + "/res/" + thread + ".json", function() { return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() {
delete status.req; delete status.req;
return ExpandThread.parse(this, thread, a); return ExpandThread.parse(this, thread, a);
}); });
@ -12208,7 +12183,7 @@
if (g.VIEW !== 'index') { if (g.VIEW !== 'index') {
return; return;
} }
url = "/" + thread.board + "/res/" + thread; url = "/" + thread.board + "/thread/" + thread;
if (tab) { if (tab) {
return $.open(url); return $.open(url);
} else { } else {
@ -13510,7 +13485,7 @@
data = { data = {
isReply: true, isReply: true,
file: { file: {
URL: '//i.4cdn.org/g/src/1334437723720.jpg', URL: '//i.4cdn.org/g/1334437723720.jpg',
name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg', name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg',
size: '276 KB', size: '276 KB',
sizeInBytes: 276 * 1024, sizeInBytes: 276 * 1024,

1071
css/style.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
"userjs": "4chan-X.user.js" "userjs": "4chan-X.user.js"
}, },
"min": { "min": {
"chrome": "32", "chrome": "33",
"firefox": "26", "firefox": "26",
"greasemonkey": "1.14" "greasemonkey": "1.14"
} }
@ -33,7 +33,7 @@
"grunt-concurrent": "~0.5.0", "grunt-concurrent": "~0.5.0",
"grunt-contrib-clean": "~0.5.0", "grunt-contrib-clean": "~0.5.0",
"grunt-contrib-coffee": "~0.10.1", "grunt-contrib-coffee": "~0.10.1",
"grunt-contrib-compress": "~0.7.0", "grunt-contrib-compress": "~0.8.0",
"grunt-contrib-concat": "~0.4.0", "grunt-contrib-concat": "~0.4.0",
"grunt-contrib-copy": "~0.5.0", "grunt-contrib-copy": "~0.5.0",
"grunt-contrib-watch": "~0.6.1", "grunt-contrib-watch": "~0.6.1",

View File

@ -12,15 +12,6 @@ PostHiding =
name: 'Post Hiding' name: 'Post Hiding'
cb: @node cb: @node
# XXX tmp conversion
$.get 'hiddenThreads', null, ({hiddenThreads}) ->
return unless hiddenThreads
for boardID, board of hiddenThreads.boards
for threadID, val of board
((PostHiding.db.data.boards[boardID] or= {})[threadID] or= {})[threadID] = val
PostHiding.db.save()
$.delete 'hiddenThreads'
node: -> node: ->
return if !@isReply and g.VIEW isnt 'index' or @isClone return if !@isReply and g.VIEW isnt 'index' or @isClone

View File

@ -42,14 +42,14 @@ Build =
name: data.filename + data.ext name: data.filename + data.ext
timestamp: "#{data.tim}#{data.ext}" timestamp: "#{data.tim}#{data.ext}"
url: if boardID is 'f' url: if boardID is 'f'
"//i.4cdn.org/#{boardID}/src/#{data.filename}#{data.ext}" "//i.4cdn.org/#{boardID}/#{data.filename}#{data.ext}"
else else
"//i.4cdn.org/#{boardID}/src/#{data.tim}#{data.ext}" "//i.4cdn.org/#{boardID}/#{data.tim}#{data.ext}"
height: data.h height: data.h
width: data.w width: data.w
MD5: data.md5 MD5: data.md5
size: data.fsize size: data.fsize
turl: "//#{Build.thumbRotate()}.t.4cdn.org/#{boardID}/thumb/#{data.tim}s.jpg" turl: "//#{Build.thumbRotate()}.t.4cdn.org/#{boardID}/#{data.tim}s.jpg"
theight: data.tn_h theight: data.tn_h
twidth: data.tn_w twidth: data.tn_w
isSpoiler: !!data.spoiler isSpoiler: !!data.spoiler
@ -184,19 +184,55 @@ Build =
if isOP and g.VIEW is 'index' if isOP and g.VIEW is 'index'
pageNum = Index.liveThreadData.keys.indexOf("#{postID}") // Index.threadsNumPerPage pageNum = Index.liveThreadData.keys.indexOf("#{postID}") // Index.threadsNumPerPage
pageIcon = " <span class=page-num title='This thread is on page #{pageNum} in the original index.'>Page #{pageNum}</span>" pageIcon = " <span class=page-num title='This thread is on page #{pageNum} in the original index.'>Page #{pageNum}</span>"
replyLink = " &nbsp; <span>[<a href='/#{boardID}/res/#{threadID}' class=replylink>Reply</a>]</span>" replyLink = " &nbsp; <span>[<a href='/#{boardID}/thread/#{threadID}' class=replylink>Reply</a>]</span>"
else else
pageIcon = replyLink = '' pageIcon = replyLink = ''
container = $.el 'div', container = $.el 'div',
id: "pc#{postID}" id: "pc#{postID}"
className: "postContainer #{if isOP then 'op' else 'reply'}Container" className: "postContainer #{if isOP then 'op' else 'reply'}Container"
innerHTML: <%= grunt.file.read('src/General/html/Build/post.html').replace(/>\s+/g, '>').replace(/\s+</g, '<').replace(/\s+/g, ' ').trim() %> innerHTML: \
(if isOP then '' else "<div class=sideArrows>&gt;&gt;</div>") +
"<div id=p#{postID} class='post #{if isOP then 'op' else 'reply'}#{
if capcode is 'admin_highlight'
' highlightPost'
else
''
}'>" +
(if isOP then fileHTML else '') +
"<div class=postInfo>" +
"<input type=checkbox name=#{postID} value=delete> " +
"<span class=subject>#{subject or ''}</span> " +
"<span class='nameBlock#{capcodeClass}'>" +
emailStart +
"<span class=name>#{name or ''}</span>" + tripcode +
capcodeStart + emailEnd + capcodeIcon + userID + flag +
' </span> ' +
"<span class=dateTime data-utc=#{dateUTC}>#{date}</span> " +
"<span class='postNum'>" +
"<a href=#{"/#{boardID}/thread/#{threadID}#p#{postID}"} title='Highlight this post'>No.</a>" +
"<a href='#{
if g.VIEW is 'thread' and g.THREADID is threadID
"javascript:quote(#{postID})"
else
"/#{boardID}/thread/#{threadID}#q#{postID}"
}' title='Quote this post'>#{postID}</a>" +
pageIcon + sticky + closed + replyLink +
'</span>' +
'</div>' +
(if isOP then '' else fileHTML) +
"<blockquote class=postMessage>#{comment or ''}</blockquote> " +
'</div>'
for quote in $$ '.quotelink', container for quote in $$ '.quotelink', container
href = quote.getAttribute 'href' href = quote.getAttribute 'href'
continue if href[0] is '/' # Cross-board quote, or board link continue if href[0] is '/' # Cross-board quote, or board link
quote.href = "/#{boardID}/res/#{href}" # Fix pathnames quote.href = "/#{boardID}/thread/#{href}" # Fix pathnames
container container
@ -208,7 +244,7 @@ Build =
$.el 'a', $.el 'a',
className: 'summary' className: 'summary'
textContent: text.join ' ' textContent: text.join ' '
href: "/#{boardID}/res/#{threadID}" href: "/#{boardID}/thread/#{threadID}"
thread: (board, data, full) -> thread: (board, data, full) ->
Build.spoilerRange[board] = data.custom_spoiler Build.spoilerRange[board] = data.custom_spoiler

View File

@ -70,7 +70,7 @@ Get =
root.textContent = "Loading post No.#{postID}..." root.textContent = "Loading post No.#{postID}..."
if threadID if threadID
$.cache "//a.4cdn.org/#{boardID}/res/#{threadID}.json", -> $.cache "//a.4cdn.org/#{boardID}/thread/#{threadID}.json", ->
Get.fetchedPost @, boardID, threadID, postID, root, context Get.fetchedPost @, boardID, threadID, postID, root, context
else if url = Redirect.to 'post', {boardID, postID} else if url = Redirect.to 'post', {boardID, postID}
$.cache url, $.cache url,
@ -200,7 +200,7 @@ Get =
width: data.media.media_w width: data.media.media_w
MD5: data.media.media_hash MD5: data.media.media_hash
size: data.media.media_size size: data.media.media_size
turl: data.media.thumb_link or "//t.4cdn.org/#{boardID}/thumb/#{data.media.preview_orig}" turl: data.media.thumb_link or "//t.4cdn.org/#{boardID}/#{data.media.preview_orig}"
theight: data.media.preview_h theight: data.media.preview_h
twidth: data.media.preview_w twidth: data.media.preview_w
isSpoiler: data.media.spoiler is '1' isSpoiler: data.media.spoiler is '1'

View File

@ -383,7 +383,7 @@ Settings =
data = data =
isReply: true isReply: true
file: file:
URL: '//i.4cdn.org/g/src/1334437723720.jpg' URL: '//i.4cdn.org/g/1334437723720.jpg'
name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg' name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg'
size: '276 KB' size: '276 KB'
sizeInBytes: 276 * 1024 sizeInBytes: 276 * 1024

View File

@ -1,36 +0,0 @@
"""#{if isOP then '' else "<div class=sideArrows>&gt;&gt;</div>"}
<div id=p#{postID} class='post #{if isOP then 'op' else 'reply'}#{
if capcodeIcon is 'admin_highlight' then
' highlightPost'
else
''
}'>
#{if isOP then fileHTML else ''}
<div class='postInfo'>
<input type=checkbox name=#{postID} value=delete>
#{' '}<span class=subject>#{subject or ''}</span>#{' '}
<span class='nameBlock#{capcodeClass}'>
#{emailStart}
<span class=name>#{name or ''}</span>
#{tripcode + capcodeStart + emailEnd + capcodeIcon + userID + flag}
</span>#{" "}
<span class=dateTime data-utc=#{dateUTC}>#{date}</span>#{' '}
<span class='postNum'>
<a href=#{"/#{boardID}/res/#{threadID}#p#{postID}"} title='Highlight this post'>No.</a>
<a href='#{
if g.VIEW is 'thread' and g.THREADID is +threadID then
"javascript:quote(#{postID})"
else
"/#{boardID}/res/#{threadID}#q#{postID}"
}' title='Quote this post'>#{postID}</a>
#{pageIcon + sticky + closed + replyLink}
</span>
</div>
#{if isOP then '' else fileHTML}
<blockquote class=postMessage>#{comment or ''}</blockquote>#{' '}
</div>"""

View File

@ -1,4 +1,4 @@
<a href="/#{thread.board}/res/#{thread.ID}" class="thumb"></a> <a href="/#{thread.board}/thread/#{thread.ID}" class="thumb"></a>
<div class="thread-stats" title="Post count / File count / Page count"> <div class="thread-stats" title="Post count / File count / Page count">
<span class="post-count">#{postCount}</span> / <span class="file-count">#{fileCount}</span> / <span class="page-count">#{pageCount}</span> <span class="post-count">#{postCount}</span> / <span class="file-count">#{fileCount}</span> / <span class="page-count">#{pageCount}</span>
<span class="thread-icons"></span> <span class="thread-icons"></span>

View File

@ -1,21 +1,8 @@
Polyfill = Polyfill =
init: -> init: ->
<% if (type === 'crx') { %> <% if (type === 'crx') { %>
@notificationPermission()
@toBlob() @toBlob()
@visibility()
<% } %> <% } %>
notificationPermission: ->
return if !window.Notification or 'permission' of Notification or !window.webkitNotifications
Object.defineProperty Notification, 'permission',
get: ->
switch webkitNotifications.checkPermission()
when 0
'granted'
when 1
'default'
when 2
'denied'
toBlob: -> toBlob: ->
HTMLCanvasElement::toBlob or= (cb) -> HTMLCanvasElement::toBlob or= (cb) ->
data = atob @toDataURL()[22..] data = atob @toDataURL()[22..]
@ -25,12 +12,3 @@ Polyfill =
for i in [0...l] by 1 for i in [0...l] by 1
ui8a[i] = data.charCodeAt i ui8a[i] = data.charCodeAt i
cb new Blob [ui8a], type: 'image/png' cb new Blob [ui8a], type: 'image/png'
visibility: ->
# page visibility API
return if 'visibilityState' of d
Object.defineProperties HTMLDocument.prototype,
visibilityState:
get: -> @webkitVisibilityState
hidden:
get: -> @webkitHidden
$.on d, 'webkitvisibilitychange', -> $.event 'visibilitychange'

View File

@ -101,7 +101,7 @@ class Post
return unless match = quotelink.href.match /// return unless match = quotelink.href.match ///
boards\.4chan\.org/ boards\.4chan\.org/
([^/]+) # boardID ([^/]+) # boardID
/res/\d+#p /thread/\d+#p
(\d+) # postID (\d+) # postID
$ $
/// ///
@ -134,7 +134,7 @@ class Post
@file.thumbURL = if that.isArchived @file.thumbURL = if that.isArchived
thumb.src thumb.src
else else
"#{location.protocol}//t.4cdn.org/#{@board}/thumb/#{@file.URL.match(/(\d+)\./)[1]}s.jpg" "#{location.protocol}//t.4cdn.org/#{@board}/#{@file.URL.match(/(\d+)\./)[1]}s.jpg"
@file.name = if nameNode = $ 'span', fileText @file.name = if nameNode = $ 'span', fileText
nameNode.title or nameNode.textContent nameNode.title or nameNode.textContent
else else

View File

@ -113,8 +113,10 @@ ImageExpand =
$.rm post.file.videoControls $.rm post.file.videoControls
delete post.file.videoControls delete post.file.videoControls
$.rmClass post.nodes.root, 'expanded-image' $.rmClass post.nodes.root, 'expanded-image'
$.rmClass thumb, 'expanding' $.rmClass post.file.thumb, 'expanding'
post.file.isExpanded = false delete post.file.isExpanding
delete post.file.isExpanded
post.file.fullImage.pause() if post.file.isVideo and post.file.fullImage
expand: (post, src, disableAutoplay) -> expand: (post, src, disableAutoplay) ->
# Do not expand images of hidden/filtered replies, or already expanded pictures. # Do not expand images of hidden/filtered replies, or already expanded pictures.
@ -195,6 +197,7 @@ ImageExpand =
error: -> error: ->
post = Get.postFromNode @ post = Get.postFromNode @
$.rm @ $.rm @
delete post.file.isReady
delete post.file.fullImage delete post.file.fullImage
# Images can error: # Images can error:
# - before the image started loading. # - before the image started loading.
@ -204,11 +207,23 @@ ImageExpand =
return return
ImageExpand.contract post ImageExpand.contract post
if @error and @error.code isnt @error.MEDIA_ERR_NETWORK # video
error = switch @error.code
when 1 then 'MEDIA_ERR_ABORTED'
when 3 then 'MEDIA_ERR_DECODE'
when 4 then 'MEDIA_ERR_SRC_NOT_SUPPORTED'
when 5 then 'MEDIA_ERR_ENCRYPTED'
post.file.error = $.el 'div',
textContent: "Playback error: #{error}"
className: 'warning'
$.after post.file.thumb, post.file.error
return
src = @src.split '/' src = @src.split '/'
if src[2] is 'i.4cdn.org' if src[2] is 'i.4cdn.org'
URL = Redirect.to 'file', URL = Redirect.to 'file',
boardID: src[3] boardID: src[3]
filename: src[5] filename: src[4].replace /\?.+$/, ''
if URL if URL
setTimeout ImageExpand.expand, 10000, post, URL setTimeout ImageExpand.expand, 10000, post, URL
return return
@ -226,7 +241,7 @@ ImageExpand =
type: 'head' type: 'head'
<% } else { %> <% } else { %>
# XXX CORS for i.4cdn.org WHEN? # XXX CORS for i.4cdn.org WHEN?
$.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: -> $.ajax "//a.4cdn.org/#{post.board}/thread/#{post.thread}.json", onload: ->
return if @status isnt 200 return if @status isnt 200
for postObj in @response.posts for postObj in @response.posts
break if postObj.no is post.ID break if postObj.no is post.ID

View File

@ -46,8 +46,10 @@ ImageHover =
el: el el: el
latestEvent: e latestEvent: e
endEvents: 'mouseout click' endEvents: 'mouseout click'
asapTest: -> (el.videoHeight or el.naturalHeight) asapTest: if post.file.isImage
noRemove: true -> el.naturalHeight
else
-> el.readyState >= el.HAVE_CURRENT_DATA
cb: -> cb: ->
if isVideo if isVideo
el.pause() el.pause()
@ -62,7 +64,7 @@ ImageHover =
if src[2] is 'i.4cdn.org' if src[2] is 'i.4cdn.org'
URL = Redirect.to 'file', URL = Redirect.to 'file',
boardID: src[3] boardID: src[3]
filename: src[5].replace /\?.+$/, '' filename: src[4].replace /\?.+$/, ''
if URL if URL
@src = URL @src = URL
return return
@ -80,7 +82,7 @@ ImageHover =
type: 'head' type: 'head'
<% } else { %> <% } else { %>
# XXX CORS for i.4cdn.org WHEN? # XXX CORS for i.4cdn.org WHEN?
$.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: -> $.ajax "//a.4cdn.org/#{post.board}/thread/#{post.thread}.json", onload: ->
return if @status isnt 200 return if @status isnt 200
for postObj in @response.posts for postObj in @response.posts
break if postObj.no is post.ID break if postObj.no is post.ID

View File

@ -49,7 +49,7 @@ ExpandThread =
expand: (thread, a, threadRoot) -> expand: (thread, a, threadRoot) ->
ExpandThread.statuses[thread] = status = {} ExpandThread.statuses[thread] = status = {}
a.textContent = ExpandThread.text '...', a.textContent.match(/\d+/g)... a.textContent = ExpandThread.text '...', a.textContent.match(/\d+/g)...
status.req = $.cache "//a.4cdn.org/#{thread.board}/res/#{thread}.json", -> status.req = $.cache "//a.4cdn.org/#{thread.board}/thread/#{thread}.json", ->
delete status.req delete status.req
ExpandThread.parse @, thread, a ExpandThread.parse @, thread, a
contract: (thread, a, threadRoot) -> contract: (thread, a, threadRoot) ->

View File

@ -232,7 +232,7 @@ Keybinds =
open: (thread, tab) -> open: (thread, tab) ->
return if g.VIEW isnt 'index' return if g.VIEW isnt 'index'
url = "/#{thread.board}/res/#{thread}" url = "/#{thread.board}/thread/#{thread}"
if tab if tab
$.open url $.open url
else else

View File

@ -244,8 +244,13 @@ ThreadUpdater =
else else
ThreadUpdater.set 'timer', 'Update' ThreadUpdater.set 'timer', 'Update'
ThreadUpdater.req?.abort() ThreadUpdater.req?.abort()
url = "//a.4cdn.org/#{ThreadUpdater.thread.board}/res/#{ThreadUpdater.thread}.json" url = "//a.4cdn.org/#{ThreadUpdater.thread.board}/thread/#{ThreadUpdater.thread}.json"
ThreadUpdater.req = $.ajax url, onloadend: ThreadUpdater.cb.load, ThreadUpdater.req = $.ajax url,
onabort: ThreadUpdater.cb.load
onloadend: ThreadUpdater.cb.load
ontimeout: ThreadUpdater.cb.load
timeout: $.MINUTE
,
whenModified: true whenModified: true
updateThreadStatus: (type, status) -> updateThreadStatus: (type, status) ->

View File

@ -122,7 +122,7 @@ ThreadWatcher =
return if data.isDead return if data.isDead
{fetchCount} = ThreadWatcher {fetchCount} = ThreadWatcher
fetchCount.fetching++ fetchCount.fetching++
$.ajax "//a.4cdn.org/#{boardID}/res/#{threadID}.json", $.ajax "//a.4cdn.org/#{boardID}/thread/#{threadID}.json",
onloadend: -> onloadend: ->
fetchCount.fetched++ fetchCount.fetched++
if fetchCount.fetched is fetchCount.fetching if fetchCount.fetched is fetchCount.fetching
@ -160,7 +160,7 @@ ThreadWatcher =
if data.isDead if data.isDead
href = Redirect.to 'thread', {boardID, threadID} href = Redirect.to 'thread', {boardID, threadID}
link = $.el 'a', link = $.el 'a',
href: href or "/#{boardID}/res/#{threadID}" href: href or "/#{boardID}/thread/#{threadID}"
textContent: data.excerpt textContent: data.excerpt
title: data.excerpt title: data.excerpt

View File

@ -223,7 +223,7 @@ QR =
$.prepend frag, $.tn '[code]' $.prepend frag, $.tn '[code]'
$.add frag, $.tn '[/code]' $.add frag, $.tn '[/code]'
for node in $$ 'br', frag for node in $$ 'br', frag
$.replace node, $.tn '\n>' $.replace node, $.tn '\n>' unless node is frag.lastElementChild
for node in $$ 's', frag for node in $$ 's', frag
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]'] $.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
for node in $$ '.prettyprint', frag for node in $$ '.prettyprint', frag
@ -800,9 +800,9 @@ QR =
QR.cooldown.set {req, post, isReply, threadID} QR.cooldown.set {req, post, isReply, threadID}
URL = if threadID is postID # new thread URL = if threadID is postID # new thread
"/#{g.BOARD}/res/#{threadID}" "/#{g.BOARD}/thread/#{threadID}"
else if g.VIEW is 'index' and !QR.cooldown.auto and Conf['Open Post in New Tab'] # replying from the index else if g.VIEW is 'index' and !QR.cooldown.auto and Conf['Open Post in New Tab'] # replying from the index
"/#{g.BOARD}/res/#{threadID}#p#{postID}" "/#{g.BOARD}/thread/#{threadID}#p#{postID}"
if URL if URL
if Conf['Open Post in New Tab'] if Conf['Open Post in New Tab']
$.open URL $.open URL

View File

@ -55,7 +55,7 @@ QuoteBacklink =
buildBacklink: (quoted, quoter) -> buildBacklink: (quoted, quoter) ->
frag = QuoteBacklink.frag.cloneNode true frag = QuoteBacklink.frag.cloneNode true
a = frag.lastElementChild a = frag.lastElementChild
a.href = "/#{quoter.board}/res/#{quoter.thread}#p#{quoter}" a.href = "/#{quoter.board}/thread/#{quoter.thread}#p#{quoter}"
a.textContent = text = QuoteBacklink.funk quoter.ID a.textContent = text = QuoteBacklink.funk quoter.ID
if quoter.isDead if quoter.isDead
$.addClass a, 'deadlink' $.addClass a, 'deadlink'

View File

@ -44,7 +44,7 @@ Quotify =
# Don't add 'deadlink' when quotifying in an archived post, # Don't add 'deadlink' when quotifying in an archived post,
# and we don't know if the post died yet. # and we don't know if the post died yet.
a = $.el 'a', a = $.el 'a',
href: "/#{boardID}/res/#{post.thread}#p#{postID}" href: "/#{boardID}/thread/#{post.thread}#p#{postID}"
className: if post.isDead then 'quotelink deadlink' else 'quotelink' className: if post.isDead then 'quotelink deadlink' else 'quotelink'
textContent: quote textContent: quote
$.extend a.dataset, {boardID, threadID: post.thread.ID, postID} $.extend a.dataset, {boardID, threadID: post.thread.ID, postID}