diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7921d945d..246c627b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@
### v1.14.5
+**v1.14.5.4** *(2018-12-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.4/builds/4chan-X-noupdate.crx)]
+- Tinyboard/vichan improvements: Process posts added by thread updating, thread expansion, and infinite scrolling scripts.
+- Fire a `PostsRemoved` event when posts are removed.
+
**v1.14.5.3** *(2018-12-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.3/builds/4chan-X-noupdate.crx)]
- Fix bugs in cross-site data access.
diff --git a/builds/4chan-X-beta.crx b/builds/4chan-X-beta.crx
index 9ef27656e..6f5af46a7 100644
Binary files a/builds/4chan-X-beta.crx and b/builds/4chan-X-beta.crx differ
diff --git a/builds/4chan-X-beta.meta.js b/builds/4chan-X-beta.meta.js
index 05cffd8f4..a75b3f8c8 100644
--- a/builds/4chan-X-beta.meta.js
+++ b/builds/4chan-X-beta.meta.js
@@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X beta
-// @version 1.14.5.3
+// @version 1.14.5.4
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
diff --git a/builds/4chan-X-beta.user.js b/builds/4chan-X-beta.user.js
index 0adb1dbd7..dac8e218b 100644
--- a/builds/4chan-X-beta.user.js
+++ b/builds/4chan-X-beta.user.js
@@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X beta
-// @version 1.14.5.3
+// @version 1.14.5.4
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
@@ -198,7 +198,7 @@ docSet = function() {
};
g = {
- VERSION: '1.14.5.3',
+ VERSION: '1.14.5.4',
NAMESPACE: '4chan X.',
boards: {}
};
@@ -1696,6 +1696,9 @@ audio.controls-added {\n\
left: 0;\n\
visibility: visible;\n\
}\n\
+#notifications:empty {\n\
+ display: none;\n\
+}\n\
:root.fixed.top-header:not(.gallery-open) #header-bar #notifications,\n\
:root.fixed.top-header #header-bar.autohide #notifications {\n\
position: absolute;\n\
@@ -7340,7 +7343,7 @@ SW = {};
},
selectors: {
board: 'form[name="postcontrols"]',
- thread: 'div[id^="thread_"]',
+ thread: 'div[id^="thread_"]:not([data-cached="yes"])',
threadDivider: 'div[id^="thread_"] > hr:last-of-type',
summary: '.omitted',
postContainer: '.reply',
@@ -8998,6 +9001,7 @@ ThreadHiding = (function() {
Index.updateHideLabel();
if (thread.catalogView && !Index.showHiddenThreads) {
$.rm(thread.catalogView.nodes.root);
+ $.event('PostsRemoved', null, Index.root);
}
if (!makeStub) {
return threadRoot.hidden = true;
@@ -9014,7 +9018,8 @@ ThreadHiding = (function() {
threadRoot.hidden = thread.isHidden = false;
Index.updateHideLabel();
if (thread.catalogView && Index.showHiddenThreads) {
- return $.rm(thread.catalogView.nodes.root);
+ $.rm(thread.catalogView.nodes.root);
+ return $.event('PostsRemoved', null, Index.root);
}
}
};
@@ -11392,6 +11397,9 @@ Index = (function() {
delete Index.pageNum;
$.rmAll(Index.root);
$.rmAll(Header.hover);
+ if (Index.loaded && Index.root.parentNode) {
+ $.event('PostsRemoved', null, Index.root);
+ }
if (Conf['Index Mode'] === 'catalog') {
Index.buildCatalog(threadIDs);
} else {
@@ -11530,8 +11538,10 @@ Polyfill = (function() {
Polyfill = {
init: function() {
+ var base;
this.toBlob();
$.global(this.toBlob);
+ (base = Element.prototype).matches || (base.matches = Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector);
},
toBlob: function() {
if (HTMLCanvasElement.prototype.toBlob) {
@@ -13372,7 +13382,9 @@ Gallery = (function() {
ref2 = $$(Site.selectors.file.thumb);
for (j = 0, len1 = ref2.length; j < len1; j++) {
postThumb = ref2[j];
- post = Get.postFromNode(postThumb);
+ if (!(post = Get.postFromNode(postThumb))) {
+ continue;
+ }
if (!((ref3 = post.file) != null ? ref3.thumb : void 0)) {
continue;
}
@@ -17106,7 +17118,7 @@ ExpandThread = (function() {
});
},
contract: function(thread, a, threadRoot) {
- var filesCount, i, inlined, len, node, num, postsCount, replies, reply, status;
+ var filesCount, i, inlined, len, num, postsCount, replies, reply, status;
status = ExpandThread.statuses[thread];
delete ExpandThread.statuses[thread];
if (status.req) {
@@ -17140,9 +17152,6 @@ ExpandThread = (function() {
filesCount = 0;
for (i = 0, len = replies.length; i < len; i++) {
reply = replies[i];
- while ((node = a.nextSibling) && node !== reply) {
- $.rm(node);
- }
if (Conf['Quote Inlining']) {
while (inlined = $('.inlined', reply)) {
inlined.click();
@@ -17154,6 +17163,9 @@ ExpandThread = (function() {
}
$.rm(reply);
}
+ if (Index.enabled) {
+ $.event('PostsRemoved', null, a.parentNode);
+ }
a.textContent = Build.summaryText('+', postsCount, filesCount);
return $.rm($('.summary-bottom', threadRoot));
},
@@ -18436,7 +18448,7 @@ RelativeDates = (function() {
var ref;
if (((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || Index.enabled) {
this.flush();
- $.on(d, 'visibilitychange ThreadUpdate', this.flush);
+ $.on(d, 'visibilitychange PostsInserted', this.flush);
}
if (Conf['Relative Post Dates']) {
return Callbacks.Post.push({
@@ -20799,7 +20811,7 @@ Unread = (function() {
}
}
$.one(d, '4chanXInitFinished', Unread.ready);
- return $.on(d, 'ThreadUpdate', Unread.onUpdate);
+ return $.on(d, 'PostsInserted', Unread.onUpdate);
},
ready: function() {
if (Conf['Remember Last Read Post'] && Conf['Scroll to Last Read Post']) {
@@ -20910,12 +20922,12 @@ Unread = (function() {
}, 7 * $.SECOND);
};
},
- onUpdate: function(e) {
- if (!e.detail[404]) {
+ onUpdate: function() {
+ return $.queueTask(function() {
Unread.setLine();
Unread.read();
- }
- return Unread.update();
+ return Unread.update();
+ });
},
readSinglePost: function(post) {
var ID;
@@ -21053,7 +21065,7 @@ UnreadIndex = (function() {
cb: this.node
});
$.on(d, 'IndexRefreshInternal', this.onIndexRefresh);
- return $.on(d, 'PostsInserted', this.onPostsInserted);
+ return $.on(d, 'PostsInserted PostsRemoved', this.onPostsInserted);
},
node: function() {
UnreadIndex.lastReadPost[this.fullID] = UnreadIndex.db.get({
@@ -21089,7 +21101,7 @@ UnreadIndex = (function() {
}
wasVisible = !!((ref = UnreadIndex.hr[thread.fullID]) != null ? ref.parentNode : void 0);
UnreadIndex.update(thread);
- if (Conf['Scroll to Last Read Post'] && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) {
+ if (Conf['Scroll to Last Read Post'] && e.type === 'PostsInserted' && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) {
return Header.scrollToIfNeeded(UnreadIndex.hr[thread.fullID], true);
}
},
@@ -24348,12 +24360,14 @@ QuoteInline = (function() {
return Unread.readSinglePost(post);
},
rm: function(quotelink, boardID, threadID, postID, context) {
- var el, inlined, isBacklink, post, qroot, ref, root;
+ var el, inlined, isBacklink, parentNode, post, qroot, ref, root;
isBacklink = $.hasClass(quotelink, 'backlink');
root = QuoteInline.findRoot(quotelink, isBacklink);
root = $.x("following-sibling::div[@data-full-i-d='" + boardID + "." + postID + "'][1]", root);
qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root);
+ parentNode = root.parentNode;
$.rm(root);
+ $.event('PostsRemoved', null, parentNode);
if (!$('.inline', qroot)) {
$.rmClass(qroot, 'hasInline');
}
@@ -24502,6 +24516,7 @@ QuotePreview = (function() {
if (!(root = this.el.firstElementChild)) {
return;
}
+ $.event('PostsRemoved', null, Header.hover);
clone = Get.postFromRoot(root);
post = clone.origin;
post.rmClone(root.dataset.clone);
@@ -25523,41 +25538,19 @@ Main = (function() {
}
},
initThread: function() {
- var board, boardID, boardObj, err, errors, j, k, len, len1, postRoot, postRoots, posts, ref, s, thread, threadRoot, threads;
+ var board, errors, posts, s, threads;
s = Site.selectors;
if ((board = $(s.board))) {
threads = [];
posts = [];
- ref = $$(s.thread, board);
- for (j = 0, len = ref.length; j < len; j++) {
- threadRoot = ref[j];
- boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD;
- thread = new Thread(+threadRoot.id.match(/\d*$/)[0], boardObj);
- thread.nodes.root = threadRoot;
- threads.push(thread);
- postRoots = $$(s.postContainer, threadRoot);
- if (Site.isOPContainerThread) {
- postRoots.unshift(threadRoot);
- }
- for (k = 0, len1 = postRoots.length; k < len1; k++) {
- postRoot = postRoots[k];
- if ($(s.comment, postRoot)) {
- try {
- posts.push(new Post(postRoot, thread, thread.board));
- } catch (_error) {
- err = _error;
- if (!errors) {
- errors = [];
- }
- errors.push({
- message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.",
- error: err
- });
- }
- }
- }
- }
- if (errors) {
+ errors = [];
+ Main.addThreadsObserver = new MutationObserver(Main.addThreads);
+ Main.addPostsObserver = new MutationObserver(Main.addPosts);
+ Main.addThreadsObserver.observe(board, {
+ childList: true
+ });
+ Main.parseThreads($$(s.thread, board), threads, posts, errors);
+ if (errors.length) {
Main.handleErrors(errors);
}
if (g.VIEW === 'thread') {
@@ -25567,9 +25560,9 @@ Main = (function() {
}
Main.callbackNodes('Thread', threads);
return Main.callbackNodesDB('Post', posts, function() {
- var l, len2, post;
- for (l = 0, len2 = posts.length; l < len2; l++) {
- post = posts[l];
+ var j, len, post;
+ for (j = 0, len = posts.length; j < len; j++) {
+ post = posts[j];
QuoteThreading.insert(post);
}
Main.expectInitFinished = true;
@@ -25580,6 +25573,115 @@ Main = (function() {
return $.event('4chanXInitFinished');
}
},
+ parseThreads: function(threadRoots, threads, posts, errors) {
+ var boardID, boardObj, j, len, postRoots, ref, thread, threadID, threadRoot;
+ for (j = 0, len = threadRoots.length; j < len; j++) {
+ threadRoot = threadRoots[j];
+ boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD;
+ threadID = +threadRoot.id.match(/\d*$/)[0];
+ if ((ref = boardObj.threads[threadID]) != null ? ref.nodes.root : void 0) {
+ return;
+ }
+ thread = new Thread(threadID, boardObj);
+ thread.nodes.root = threadRoot;
+ threads.push(thread);
+ postRoots = $$(Site.selectors.postContainer, threadRoot);
+ if (Site.isOPContainerThread) {
+ postRoots.unshift(threadRoot);
+ }
+ Main.parsePosts(postRoots, thread, posts, errors);
+ Main.addPostsObserver.observe(threadRoot, {
+ childList: true
+ });
+ }
+ },
+ parsePosts: function(postRoots, thread, posts, errors) {
+ var err, j, len, postRoot;
+ for (j = 0, len = postRoots.length; j < len; j++) {
+ postRoot = postRoots[j];
+ if (!postRoot.dataset.fullID && $(Site.selectors.comment, postRoot)) {
+ try {
+ posts.push(new Post(postRoot, thread, thread.board));
+ } catch (_error) {
+ err = _error;
+ errors.push({
+ message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.",
+ error: err
+ });
+ }
+ }
+ }
+ },
+ addThreads: function(records) {
+ var errors, j, k, len, len1, node, posts, record, ref, threadRoots, threads;
+ threadRoots = [];
+ for (j = 0, len = records.length; j < len; j++) {
+ record = records[j];
+ ref = record.addedNodes;
+ for (k = 0, len1 = ref.length; k < len1; k++) {
+ node = ref[k];
+ if (node.matches(Site.selectors.thread)) {
+ threadRoots.push(node);
+ }
+ }
+ }
+ if (!threadRoots.length) {
+ return;
+ }
+ threads = [];
+ posts = [];
+ errors = [];
+ Main.parseThreads(threadRoots, threads, posts, errors);
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ Main.callbackNodes('Thread', threads);
+ return Main.callbackNodesDB('Post', posts, function() {
+ return $.event('PostsInserted', null, records[0].target);
+ });
+ },
+ addPosts: function(records) {
+ var anyRemoved, el, errors, j, k, len, len1, n, posts, record, ref, ref1, thread, threads, threadsRM;
+ threads = [];
+ threadsRM = [];
+ posts = [];
+ errors = [];
+ for (j = 0, len = records.length; j < len; j++) {
+ record = records[j];
+ thread = Get.threadFromRoot(record.target);
+ n = posts.length;
+ Main.parsePosts(record.addedNodes, thread, posts, errors);
+ if (posts.length > n && indexOf.call(threads, thread) < 0) {
+ threads.push(thread);
+ }
+ anyRemoved = false;
+ ref = record.removedNodes;
+ for (k = 0, len1 = ref.length; k < len1; k++) {
+ el = ref[k];
+ if (((ref1 = Get.postFromRoot(el)) != null ? ref1.nodes.root : void 0) === el && !doc.contains(el)) {
+ anyRemoved = true;
+ break;
+ }
+ }
+ if (anyRemoved && indexOf.call(threadsRM, thread) < 0) {
+ threadsRM.push(thread);
+ }
+ }
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ return Main.callbackNodesDB('Post', posts, function() {
+ var l, len2, len3, m;
+ for (l = 0, len2 = threads.length; l < len2; l++) {
+ thread = threads[l];
+ $.event('PostsInserted', null, thread.nodes.root);
+ }
+ for (m = 0, len3 = threadsRM.length; m < len3; m++) {
+ thread = threadsRM[m];
+ $.event('PostsRemoved', null, thread.nodes.root);
+ }
+ });
+ },
callbackNodes: function(klass, nodes) {
var cb, i, node;
i = 0;
diff --git a/builds/4chan-X-noupdate.crx b/builds/4chan-X-noupdate.crx
index 8c17e5cd5..887785faf 100644
Binary files a/builds/4chan-X-noupdate.crx and b/builds/4chan-X-noupdate.crx differ
diff --git a/builds/4chan-X-noupdate.user.js b/builds/4chan-X-noupdate.user.js
index e8d49dbdd..7f64bb414 100644
--- a/builds/4chan-X-noupdate.user.js
+++ b/builds/4chan-X-noupdate.user.js
@@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X
-// @version 1.14.5.3
+// @version 1.14.5.4
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
@@ -198,7 +198,7 @@ docSet = function() {
};
g = {
- VERSION: '1.14.5.3',
+ VERSION: '1.14.5.4',
NAMESPACE: '4chan X.',
boards: {}
};
@@ -1696,6 +1696,9 @@ audio.controls-added {\n\
left: 0;\n\
visibility: visible;\n\
}\n\
+#notifications:empty {\n\
+ display: none;\n\
+}\n\
:root.fixed.top-header:not(.gallery-open) #header-bar #notifications,\n\
:root.fixed.top-header #header-bar.autohide #notifications {\n\
position: absolute;\n\
@@ -7340,7 +7343,7 @@ SW = {};
},
selectors: {
board: 'form[name="postcontrols"]',
- thread: 'div[id^="thread_"]',
+ thread: 'div[id^="thread_"]:not([data-cached="yes"])',
threadDivider: 'div[id^="thread_"] > hr:last-of-type',
summary: '.omitted',
postContainer: '.reply',
@@ -8998,6 +9001,7 @@ ThreadHiding = (function() {
Index.updateHideLabel();
if (thread.catalogView && !Index.showHiddenThreads) {
$.rm(thread.catalogView.nodes.root);
+ $.event('PostsRemoved', null, Index.root);
}
if (!makeStub) {
return threadRoot.hidden = true;
@@ -9014,7 +9018,8 @@ ThreadHiding = (function() {
threadRoot.hidden = thread.isHidden = false;
Index.updateHideLabel();
if (thread.catalogView && Index.showHiddenThreads) {
- return $.rm(thread.catalogView.nodes.root);
+ $.rm(thread.catalogView.nodes.root);
+ return $.event('PostsRemoved', null, Index.root);
}
}
};
@@ -11392,6 +11397,9 @@ Index = (function() {
delete Index.pageNum;
$.rmAll(Index.root);
$.rmAll(Header.hover);
+ if (Index.loaded && Index.root.parentNode) {
+ $.event('PostsRemoved', null, Index.root);
+ }
if (Conf['Index Mode'] === 'catalog') {
Index.buildCatalog(threadIDs);
} else {
@@ -11530,8 +11538,10 @@ Polyfill = (function() {
Polyfill = {
init: function() {
+ var base;
this.toBlob();
$.global(this.toBlob);
+ (base = Element.prototype).matches || (base.matches = Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector);
},
toBlob: function() {
if (HTMLCanvasElement.prototype.toBlob) {
@@ -13372,7 +13382,9 @@ Gallery = (function() {
ref2 = $$(Site.selectors.file.thumb);
for (j = 0, len1 = ref2.length; j < len1; j++) {
postThumb = ref2[j];
- post = Get.postFromNode(postThumb);
+ if (!(post = Get.postFromNode(postThumb))) {
+ continue;
+ }
if (!((ref3 = post.file) != null ? ref3.thumb : void 0)) {
continue;
}
@@ -17106,7 +17118,7 @@ ExpandThread = (function() {
});
},
contract: function(thread, a, threadRoot) {
- var filesCount, i, inlined, len, node, num, postsCount, replies, reply, status;
+ var filesCount, i, inlined, len, num, postsCount, replies, reply, status;
status = ExpandThread.statuses[thread];
delete ExpandThread.statuses[thread];
if (status.req) {
@@ -17140,9 +17152,6 @@ ExpandThread = (function() {
filesCount = 0;
for (i = 0, len = replies.length; i < len; i++) {
reply = replies[i];
- while ((node = a.nextSibling) && node !== reply) {
- $.rm(node);
- }
if (Conf['Quote Inlining']) {
while (inlined = $('.inlined', reply)) {
inlined.click();
@@ -17154,6 +17163,9 @@ ExpandThread = (function() {
}
$.rm(reply);
}
+ if (Index.enabled) {
+ $.event('PostsRemoved', null, a.parentNode);
+ }
a.textContent = Build.summaryText('+', postsCount, filesCount);
return $.rm($('.summary-bottom', threadRoot));
},
@@ -18436,7 +18448,7 @@ RelativeDates = (function() {
var ref;
if (((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || Index.enabled) {
this.flush();
- $.on(d, 'visibilitychange ThreadUpdate', this.flush);
+ $.on(d, 'visibilitychange PostsInserted', this.flush);
}
if (Conf['Relative Post Dates']) {
return Callbacks.Post.push({
@@ -20799,7 +20811,7 @@ Unread = (function() {
}
}
$.one(d, '4chanXInitFinished', Unread.ready);
- return $.on(d, 'ThreadUpdate', Unread.onUpdate);
+ return $.on(d, 'PostsInserted', Unread.onUpdate);
},
ready: function() {
if (Conf['Remember Last Read Post'] && Conf['Scroll to Last Read Post']) {
@@ -20910,12 +20922,12 @@ Unread = (function() {
}, 7 * $.SECOND);
};
},
- onUpdate: function(e) {
- if (!e.detail[404]) {
+ onUpdate: function() {
+ return $.queueTask(function() {
Unread.setLine();
Unread.read();
- }
- return Unread.update();
+ return Unread.update();
+ });
},
readSinglePost: function(post) {
var ID;
@@ -21053,7 +21065,7 @@ UnreadIndex = (function() {
cb: this.node
});
$.on(d, 'IndexRefreshInternal', this.onIndexRefresh);
- return $.on(d, 'PostsInserted', this.onPostsInserted);
+ return $.on(d, 'PostsInserted PostsRemoved', this.onPostsInserted);
},
node: function() {
UnreadIndex.lastReadPost[this.fullID] = UnreadIndex.db.get({
@@ -21089,7 +21101,7 @@ UnreadIndex = (function() {
}
wasVisible = !!((ref = UnreadIndex.hr[thread.fullID]) != null ? ref.parentNode : void 0);
UnreadIndex.update(thread);
- if (Conf['Scroll to Last Read Post'] && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) {
+ if (Conf['Scroll to Last Read Post'] && e.type === 'PostsInserted' && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) {
return Header.scrollToIfNeeded(UnreadIndex.hr[thread.fullID], true);
}
},
@@ -24348,12 +24360,14 @@ QuoteInline = (function() {
return Unread.readSinglePost(post);
},
rm: function(quotelink, boardID, threadID, postID, context) {
- var el, inlined, isBacklink, post, qroot, ref, root;
+ var el, inlined, isBacklink, parentNode, post, qroot, ref, root;
isBacklink = $.hasClass(quotelink, 'backlink');
root = QuoteInline.findRoot(quotelink, isBacklink);
root = $.x("following-sibling::div[@data-full-i-d='" + boardID + "." + postID + "'][1]", root);
qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root);
+ parentNode = root.parentNode;
$.rm(root);
+ $.event('PostsRemoved', null, parentNode);
if (!$('.inline', qroot)) {
$.rmClass(qroot, 'hasInline');
}
@@ -24502,6 +24516,7 @@ QuotePreview = (function() {
if (!(root = this.el.firstElementChild)) {
return;
}
+ $.event('PostsRemoved', null, Header.hover);
clone = Get.postFromRoot(root);
post = clone.origin;
post.rmClone(root.dataset.clone);
@@ -25523,41 +25538,19 @@ Main = (function() {
}
},
initThread: function() {
- var board, boardID, boardObj, err, errors, j, k, len, len1, postRoot, postRoots, posts, ref, s, thread, threadRoot, threads;
+ var board, errors, posts, s, threads;
s = Site.selectors;
if ((board = $(s.board))) {
threads = [];
posts = [];
- ref = $$(s.thread, board);
- for (j = 0, len = ref.length; j < len; j++) {
- threadRoot = ref[j];
- boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD;
- thread = new Thread(+threadRoot.id.match(/\d*$/)[0], boardObj);
- thread.nodes.root = threadRoot;
- threads.push(thread);
- postRoots = $$(s.postContainer, threadRoot);
- if (Site.isOPContainerThread) {
- postRoots.unshift(threadRoot);
- }
- for (k = 0, len1 = postRoots.length; k < len1; k++) {
- postRoot = postRoots[k];
- if ($(s.comment, postRoot)) {
- try {
- posts.push(new Post(postRoot, thread, thread.board));
- } catch (_error) {
- err = _error;
- if (!errors) {
- errors = [];
- }
- errors.push({
- message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.",
- error: err
- });
- }
- }
- }
- }
- if (errors) {
+ errors = [];
+ Main.addThreadsObserver = new MutationObserver(Main.addThreads);
+ Main.addPostsObserver = new MutationObserver(Main.addPosts);
+ Main.addThreadsObserver.observe(board, {
+ childList: true
+ });
+ Main.parseThreads($$(s.thread, board), threads, posts, errors);
+ if (errors.length) {
Main.handleErrors(errors);
}
if (g.VIEW === 'thread') {
@@ -25567,9 +25560,9 @@ Main = (function() {
}
Main.callbackNodes('Thread', threads);
return Main.callbackNodesDB('Post', posts, function() {
- var l, len2, post;
- for (l = 0, len2 = posts.length; l < len2; l++) {
- post = posts[l];
+ var j, len, post;
+ for (j = 0, len = posts.length; j < len; j++) {
+ post = posts[j];
QuoteThreading.insert(post);
}
Main.expectInitFinished = true;
@@ -25580,6 +25573,115 @@ Main = (function() {
return $.event('4chanXInitFinished');
}
},
+ parseThreads: function(threadRoots, threads, posts, errors) {
+ var boardID, boardObj, j, len, postRoots, ref, thread, threadID, threadRoot;
+ for (j = 0, len = threadRoots.length; j < len; j++) {
+ threadRoot = threadRoots[j];
+ boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD;
+ threadID = +threadRoot.id.match(/\d*$/)[0];
+ if ((ref = boardObj.threads[threadID]) != null ? ref.nodes.root : void 0) {
+ return;
+ }
+ thread = new Thread(threadID, boardObj);
+ thread.nodes.root = threadRoot;
+ threads.push(thread);
+ postRoots = $$(Site.selectors.postContainer, threadRoot);
+ if (Site.isOPContainerThread) {
+ postRoots.unshift(threadRoot);
+ }
+ Main.parsePosts(postRoots, thread, posts, errors);
+ Main.addPostsObserver.observe(threadRoot, {
+ childList: true
+ });
+ }
+ },
+ parsePosts: function(postRoots, thread, posts, errors) {
+ var err, j, len, postRoot;
+ for (j = 0, len = postRoots.length; j < len; j++) {
+ postRoot = postRoots[j];
+ if (!postRoot.dataset.fullID && $(Site.selectors.comment, postRoot)) {
+ try {
+ posts.push(new Post(postRoot, thread, thread.board));
+ } catch (_error) {
+ err = _error;
+ errors.push({
+ message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.",
+ error: err
+ });
+ }
+ }
+ }
+ },
+ addThreads: function(records) {
+ var errors, j, k, len, len1, node, posts, record, ref, threadRoots, threads;
+ threadRoots = [];
+ for (j = 0, len = records.length; j < len; j++) {
+ record = records[j];
+ ref = record.addedNodes;
+ for (k = 0, len1 = ref.length; k < len1; k++) {
+ node = ref[k];
+ if (node.matches(Site.selectors.thread)) {
+ threadRoots.push(node);
+ }
+ }
+ }
+ if (!threadRoots.length) {
+ return;
+ }
+ threads = [];
+ posts = [];
+ errors = [];
+ Main.parseThreads(threadRoots, threads, posts, errors);
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ Main.callbackNodes('Thread', threads);
+ return Main.callbackNodesDB('Post', posts, function() {
+ return $.event('PostsInserted', null, records[0].target);
+ });
+ },
+ addPosts: function(records) {
+ var anyRemoved, el, errors, j, k, len, len1, n, posts, record, ref, ref1, thread, threads, threadsRM;
+ threads = [];
+ threadsRM = [];
+ posts = [];
+ errors = [];
+ for (j = 0, len = records.length; j < len; j++) {
+ record = records[j];
+ thread = Get.threadFromRoot(record.target);
+ n = posts.length;
+ Main.parsePosts(record.addedNodes, thread, posts, errors);
+ if (posts.length > n && indexOf.call(threads, thread) < 0) {
+ threads.push(thread);
+ }
+ anyRemoved = false;
+ ref = record.removedNodes;
+ for (k = 0, len1 = ref.length; k < len1; k++) {
+ el = ref[k];
+ if (((ref1 = Get.postFromRoot(el)) != null ? ref1.nodes.root : void 0) === el && !doc.contains(el)) {
+ anyRemoved = true;
+ break;
+ }
+ }
+ if (anyRemoved && indexOf.call(threadsRM, thread) < 0) {
+ threadsRM.push(thread);
+ }
+ }
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ return Main.callbackNodesDB('Post', posts, function() {
+ var l, len2, len3, m;
+ for (l = 0, len2 = threads.length; l < len2; l++) {
+ thread = threads[l];
+ $.event('PostsInserted', null, thread.nodes.root);
+ }
+ for (m = 0, len3 = threadsRM.length; m < len3; m++) {
+ thread = threadsRM[m];
+ $.event('PostsRemoved', null, thread.nodes.root);
+ }
+ });
+ },
callbackNodes: function(klass, nodes) {
var cb, i, node;
i = 0;
diff --git a/builds/4chan-X.crx b/builds/4chan-X.crx
index 084bf1a76..02a6c3d19 100644
Binary files a/builds/4chan-X.crx and b/builds/4chan-X.crx differ
diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js
index 6c419e40d..e0d7dafd4 100644
--- a/builds/4chan-X.meta.js
+++ b/builds/4chan-X.meta.js
@@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X
-// @version 1.14.5.3
+// @version 1.14.5.4
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js
index 942521ebb..b80ed68eb 100644
--- a/builds/4chan-X.user.js
+++ b/builds/4chan-X.user.js
@@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X
-// @version 1.14.5.3
+// @version 1.14.5.4
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
@@ -198,7 +198,7 @@ docSet = function() {
};
g = {
- VERSION: '1.14.5.3',
+ VERSION: '1.14.5.4',
NAMESPACE: '4chan X.',
boards: {}
};
@@ -1696,6 +1696,9 @@ audio.controls-added {\n\
left: 0;\n\
visibility: visible;\n\
}\n\
+#notifications:empty {\n\
+ display: none;\n\
+}\n\
:root.fixed.top-header:not(.gallery-open) #header-bar #notifications,\n\
:root.fixed.top-header #header-bar.autohide #notifications {\n\
position: absolute;\n\
@@ -7340,7 +7343,7 @@ SW = {};
},
selectors: {
board: 'form[name="postcontrols"]',
- thread: 'div[id^="thread_"]',
+ thread: 'div[id^="thread_"]:not([data-cached="yes"])',
threadDivider: 'div[id^="thread_"] > hr:last-of-type',
summary: '.omitted',
postContainer: '.reply',
@@ -8998,6 +9001,7 @@ ThreadHiding = (function() {
Index.updateHideLabel();
if (thread.catalogView && !Index.showHiddenThreads) {
$.rm(thread.catalogView.nodes.root);
+ $.event('PostsRemoved', null, Index.root);
}
if (!makeStub) {
return threadRoot.hidden = true;
@@ -9014,7 +9018,8 @@ ThreadHiding = (function() {
threadRoot.hidden = thread.isHidden = false;
Index.updateHideLabel();
if (thread.catalogView && Index.showHiddenThreads) {
- return $.rm(thread.catalogView.nodes.root);
+ $.rm(thread.catalogView.nodes.root);
+ return $.event('PostsRemoved', null, Index.root);
}
}
};
@@ -11392,6 +11397,9 @@ Index = (function() {
delete Index.pageNum;
$.rmAll(Index.root);
$.rmAll(Header.hover);
+ if (Index.loaded && Index.root.parentNode) {
+ $.event('PostsRemoved', null, Index.root);
+ }
if (Conf['Index Mode'] === 'catalog') {
Index.buildCatalog(threadIDs);
} else {
@@ -11530,8 +11538,10 @@ Polyfill = (function() {
Polyfill = {
init: function() {
+ var base;
this.toBlob();
$.global(this.toBlob);
+ (base = Element.prototype).matches || (base.matches = Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector);
},
toBlob: function() {
if (HTMLCanvasElement.prototype.toBlob) {
@@ -13372,7 +13382,9 @@ Gallery = (function() {
ref2 = $$(Site.selectors.file.thumb);
for (j = 0, len1 = ref2.length; j < len1; j++) {
postThumb = ref2[j];
- post = Get.postFromNode(postThumb);
+ if (!(post = Get.postFromNode(postThumb))) {
+ continue;
+ }
if (!((ref3 = post.file) != null ? ref3.thumb : void 0)) {
continue;
}
@@ -17106,7 +17118,7 @@ ExpandThread = (function() {
});
},
contract: function(thread, a, threadRoot) {
- var filesCount, i, inlined, len, node, num, postsCount, replies, reply, status;
+ var filesCount, i, inlined, len, num, postsCount, replies, reply, status;
status = ExpandThread.statuses[thread];
delete ExpandThread.statuses[thread];
if (status.req) {
@@ -17140,9 +17152,6 @@ ExpandThread = (function() {
filesCount = 0;
for (i = 0, len = replies.length; i < len; i++) {
reply = replies[i];
- while ((node = a.nextSibling) && node !== reply) {
- $.rm(node);
- }
if (Conf['Quote Inlining']) {
while (inlined = $('.inlined', reply)) {
inlined.click();
@@ -17154,6 +17163,9 @@ ExpandThread = (function() {
}
$.rm(reply);
}
+ if (Index.enabled) {
+ $.event('PostsRemoved', null, a.parentNode);
+ }
a.textContent = Build.summaryText('+', postsCount, filesCount);
return $.rm($('.summary-bottom', threadRoot));
},
@@ -18436,7 +18448,7 @@ RelativeDates = (function() {
var ref;
if (((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || Index.enabled) {
this.flush();
- $.on(d, 'visibilitychange ThreadUpdate', this.flush);
+ $.on(d, 'visibilitychange PostsInserted', this.flush);
}
if (Conf['Relative Post Dates']) {
return Callbacks.Post.push({
@@ -20799,7 +20811,7 @@ Unread = (function() {
}
}
$.one(d, '4chanXInitFinished', Unread.ready);
- return $.on(d, 'ThreadUpdate', Unread.onUpdate);
+ return $.on(d, 'PostsInserted', Unread.onUpdate);
},
ready: function() {
if (Conf['Remember Last Read Post'] && Conf['Scroll to Last Read Post']) {
@@ -20910,12 +20922,12 @@ Unread = (function() {
}, 7 * $.SECOND);
};
},
- onUpdate: function(e) {
- if (!e.detail[404]) {
+ onUpdate: function() {
+ return $.queueTask(function() {
Unread.setLine();
Unread.read();
- }
- return Unread.update();
+ return Unread.update();
+ });
},
readSinglePost: function(post) {
var ID;
@@ -21053,7 +21065,7 @@ UnreadIndex = (function() {
cb: this.node
});
$.on(d, 'IndexRefreshInternal', this.onIndexRefresh);
- return $.on(d, 'PostsInserted', this.onPostsInserted);
+ return $.on(d, 'PostsInserted PostsRemoved', this.onPostsInserted);
},
node: function() {
UnreadIndex.lastReadPost[this.fullID] = UnreadIndex.db.get({
@@ -21089,7 +21101,7 @@ UnreadIndex = (function() {
}
wasVisible = !!((ref = UnreadIndex.hr[thread.fullID]) != null ? ref.parentNode : void 0);
UnreadIndex.update(thread);
- if (Conf['Scroll to Last Read Post'] && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) {
+ if (Conf['Scroll to Last Read Post'] && e.type === 'PostsInserted' && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) {
return Header.scrollToIfNeeded(UnreadIndex.hr[thread.fullID], true);
}
},
@@ -24348,12 +24360,14 @@ QuoteInline = (function() {
return Unread.readSinglePost(post);
},
rm: function(quotelink, boardID, threadID, postID, context) {
- var el, inlined, isBacklink, post, qroot, ref, root;
+ var el, inlined, isBacklink, parentNode, post, qroot, ref, root;
isBacklink = $.hasClass(quotelink, 'backlink');
root = QuoteInline.findRoot(quotelink, isBacklink);
root = $.x("following-sibling::div[@data-full-i-d='" + boardID + "." + postID + "'][1]", root);
qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root);
+ parentNode = root.parentNode;
$.rm(root);
+ $.event('PostsRemoved', null, parentNode);
if (!$('.inline', qroot)) {
$.rmClass(qroot, 'hasInline');
}
@@ -24502,6 +24516,7 @@ QuotePreview = (function() {
if (!(root = this.el.firstElementChild)) {
return;
}
+ $.event('PostsRemoved', null, Header.hover);
clone = Get.postFromRoot(root);
post = clone.origin;
post.rmClone(root.dataset.clone);
@@ -25523,41 +25538,19 @@ Main = (function() {
}
},
initThread: function() {
- var board, boardID, boardObj, err, errors, j, k, len, len1, postRoot, postRoots, posts, ref, s, thread, threadRoot, threads;
+ var board, errors, posts, s, threads;
s = Site.selectors;
if ((board = $(s.board))) {
threads = [];
posts = [];
- ref = $$(s.thread, board);
- for (j = 0, len = ref.length; j < len; j++) {
- threadRoot = ref[j];
- boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD;
- thread = new Thread(+threadRoot.id.match(/\d*$/)[0], boardObj);
- thread.nodes.root = threadRoot;
- threads.push(thread);
- postRoots = $$(s.postContainer, threadRoot);
- if (Site.isOPContainerThread) {
- postRoots.unshift(threadRoot);
- }
- for (k = 0, len1 = postRoots.length; k < len1; k++) {
- postRoot = postRoots[k];
- if ($(s.comment, postRoot)) {
- try {
- posts.push(new Post(postRoot, thread, thread.board));
- } catch (_error) {
- err = _error;
- if (!errors) {
- errors = [];
- }
- errors.push({
- message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.",
- error: err
- });
- }
- }
- }
- }
- if (errors) {
+ errors = [];
+ Main.addThreadsObserver = new MutationObserver(Main.addThreads);
+ Main.addPostsObserver = new MutationObserver(Main.addPosts);
+ Main.addThreadsObserver.observe(board, {
+ childList: true
+ });
+ Main.parseThreads($$(s.thread, board), threads, posts, errors);
+ if (errors.length) {
Main.handleErrors(errors);
}
if (g.VIEW === 'thread') {
@@ -25567,9 +25560,9 @@ Main = (function() {
}
Main.callbackNodes('Thread', threads);
return Main.callbackNodesDB('Post', posts, function() {
- var l, len2, post;
- for (l = 0, len2 = posts.length; l < len2; l++) {
- post = posts[l];
+ var j, len, post;
+ for (j = 0, len = posts.length; j < len; j++) {
+ post = posts[j];
QuoteThreading.insert(post);
}
Main.expectInitFinished = true;
@@ -25580,6 +25573,115 @@ Main = (function() {
return $.event('4chanXInitFinished');
}
},
+ parseThreads: function(threadRoots, threads, posts, errors) {
+ var boardID, boardObj, j, len, postRoots, ref, thread, threadID, threadRoot;
+ for (j = 0, len = threadRoots.length; j < len; j++) {
+ threadRoot = threadRoots[j];
+ boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD;
+ threadID = +threadRoot.id.match(/\d*$/)[0];
+ if ((ref = boardObj.threads[threadID]) != null ? ref.nodes.root : void 0) {
+ return;
+ }
+ thread = new Thread(threadID, boardObj);
+ thread.nodes.root = threadRoot;
+ threads.push(thread);
+ postRoots = $$(Site.selectors.postContainer, threadRoot);
+ if (Site.isOPContainerThread) {
+ postRoots.unshift(threadRoot);
+ }
+ Main.parsePosts(postRoots, thread, posts, errors);
+ Main.addPostsObserver.observe(threadRoot, {
+ childList: true
+ });
+ }
+ },
+ parsePosts: function(postRoots, thread, posts, errors) {
+ var err, j, len, postRoot;
+ for (j = 0, len = postRoots.length; j < len; j++) {
+ postRoot = postRoots[j];
+ if (!postRoot.dataset.fullID && $(Site.selectors.comment, postRoot)) {
+ try {
+ posts.push(new Post(postRoot, thread, thread.board));
+ } catch (_error) {
+ err = _error;
+ errors.push({
+ message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.",
+ error: err
+ });
+ }
+ }
+ }
+ },
+ addThreads: function(records) {
+ var errors, j, k, len, len1, node, posts, record, ref, threadRoots, threads;
+ threadRoots = [];
+ for (j = 0, len = records.length; j < len; j++) {
+ record = records[j];
+ ref = record.addedNodes;
+ for (k = 0, len1 = ref.length; k < len1; k++) {
+ node = ref[k];
+ if (node.matches(Site.selectors.thread)) {
+ threadRoots.push(node);
+ }
+ }
+ }
+ if (!threadRoots.length) {
+ return;
+ }
+ threads = [];
+ posts = [];
+ errors = [];
+ Main.parseThreads(threadRoots, threads, posts, errors);
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ Main.callbackNodes('Thread', threads);
+ return Main.callbackNodesDB('Post', posts, function() {
+ return $.event('PostsInserted', null, records[0].target);
+ });
+ },
+ addPosts: function(records) {
+ var anyRemoved, el, errors, j, k, len, len1, n, posts, record, ref, ref1, thread, threads, threadsRM;
+ threads = [];
+ threadsRM = [];
+ posts = [];
+ errors = [];
+ for (j = 0, len = records.length; j < len; j++) {
+ record = records[j];
+ thread = Get.threadFromRoot(record.target);
+ n = posts.length;
+ Main.parsePosts(record.addedNodes, thread, posts, errors);
+ if (posts.length > n && indexOf.call(threads, thread) < 0) {
+ threads.push(thread);
+ }
+ anyRemoved = false;
+ ref = record.removedNodes;
+ for (k = 0, len1 = ref.length; k < len1; k++) {
+ el = ref[k];
+ if (((ref1 = Get.postFromRoot(el)) != null ? ref1.nodes.root : void 0) === el && !doc.contains(el)) {
+ anyRemoved = true;
+ break;
+ }
+ }
+ if (anyRemoved && indexOf.call(threadsRM, thread) < 0) {
+ threadsRM.push(thread);
+ }
+ }
+ if (errors.length) {
+ Main.handleErrors(errors);
+ }
+ return Main.callbackNodesDB('Post', posts, function() {
+ var l, len2, len3, m;
+ for (l = 0, len2 = threads.length; l < len2; l++) {
+ thread = threads[l];
+ $.event('PostsInserted', null, thread.nodes.root);
+ }
+ for (m = 0, len3 = threadsRM.length; m < len3; m++) {
+ thread = threadsRM[m];
+ $.event('PostsRemoved', null, thread.nodes.root);
+ }
+ });
+ },
callbackNodes: function(klass, nodes) {
var cb, i, node;
i = 0;
diff --git a/builds/4chan-X.zip b/builds/4chan-X.zip
index 24fe0a7eb..635d09b22 100644
Binary files a/builds/4chan-X.zip and b/builds/4chan-X.zip differ
diff --git a/builds/updates-beta.json b/builds/updates-beta.json
index ca71b9829..d56635b8c 100644
--- a/builds/updates-beta.json
+++ b/builds/updates-beta.json
@@ -3,7 +3,7 @@
"4chan-x@4chan-x.net": {
"updates": [
{
- "version": "1.14.5.3",
+ "version": "1.14.5.4",
"update_link": "https://www.4chan-x.net/builds/4chan-X-beta.crx"
}
]
diff --git a/builds/updates-beta.xml b/builds/updates-beta.xml
index 8f0f88358..5f57ba639 100644
--- a/builds/updates-beta.xml
+++ b/builds/updates-beta.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/builds/updates.json b/builds/updates.json
index f17125d3f..fa1002096 100644
--- a/builds/updates.json
+++ b/builds/updates.json
@@ -3,7 +3,7 @@
"4chan-x@4chan-x.net": {
"updates": [
{
- "version": "1.14.5.3",
+ "version": "1.14.5.4",
"update_link": "https://www.4chan-x.net/builds/4chan-X.crx"
}
]
diff --git a/builds/updates.xml b/builds/updates.xml
index ba1264495..c99867b0b 100644
--- a/builds/updates.xml
+++ b/builds/updates.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/version.json b/version.json
index d949e0ad0..e1a297484 100644
--- a/version.json
+++ b/version.json
@@ -1,4 +1,4 @@
{
- "version": "1.14.5.3",
- "date": "2018-12-07T07:37:49.493Z"
+ "version": "1.14.5.4",
+ "date": "2018-12-08T22:05:28.502Z"
}
\ No newline at end of file