diff --git a/LICENSE b/LICENSE
index 303f4b90c..9b1cd14c9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,5 @@
/*
-* appchan x - Version 2.7.5 - 2014-01-04
+* appchan x - Version 2.7.5 - 2014-01-06
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js
index f739ebaff..463799254 100644
--- a/builds/appchan-x.user.js
+++ b/builds/appchan-x.user.js
@@ -22,7 +22,7 @@
// ==/UserScript==
/*
-* appchan x - Version 2.7.5 - 2014-01-04
+* appchan x - Version 2.7.5 - 2014-01-06
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@@ -110,7 +110,7 @@
'use strict';
(function() {
- var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, CatalogLinks, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
+ var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, CatalogLinks, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__slice = [].slice,
__hasProp = {}.hasOwnProperty,
@@ -3825,6 +3825,99 @@
})();
+ RandomAccessList = (function() {
+ function RandomAccessList() {
+ this.length = 0;
+ }
+
+ RandomAccessList.prototype.push = function(item) {
+ var ID, last;
+ ID = item.ID;
+ if (this[ID]) {
+ return;
+ }
+ last = this.last;
+ item.prev = last;
+ this[ID] = item;
+ this.last = last ? last.next = item : this.first = item;
+ return this.length++;
+ };
+
+ RandomAccessList.prototype.after = function(root, item) {
+ var next;
+ if (item.prev === root) {
+ return;
+ }
+ this.rmi(item);
+ next = root.next;
+ root.next = item;
+ item.prev = root;
+ item.next = next;
+ return next.prev = item;
+ };
+
+ RandomAccessList.prototype.prepend = function(item) {
+ var first;
+ first = this.first;
+ if (item === first || !this[item.ID]) {
+ return;
+ }
+ this.rmi(item);
+ item.next = first;
+ first.prev = item;
+ this.first = item;
+ return delete item.prev;
+ };
+
+ RandomAccessList.prototype.shift = function() {
+ return this.rm(this.first.ID);
+ };
+
+ RandomAccessList.prototype.rm = function(ID) {
+ var item;
+ item = this[ID];
+ if (!item) {
+ return;
+ }
+ delete this[ID];
+ this.length--;
+ this.rmi(item);
+ delete item.next;
+ return delete item.prev;
+ };
+
+ RandomAccessList.prototype.rmi = function(item) {
+ var next, prev;
+ prev = item.prev, next = item.next;
+ if (prev) {
+ prev.next = next;
+ } else {
+ this.first = next;
+ }
+ if (next) {
+ return next.prev = prev;
+ } else {
+ return this.last = prev;
+ }
+ };
+
+ RandomAccessList.prototype.closest = function(ID) {
+ var item, prev;
+ item = this.first;
+ while (item) {
+ if (item.ID > ID) {
+ prev = item.prev;
+ break;
+ }
+ item = item.next;
+ }
+ return (prev ? prev.ID : -1);
+ };
+
+ return RandomAccessList;
+
+ })();
+
Polyfill = {
init: function() {},
notificationPermission: function() {
@@ -7126,108 +7219,120 @@
innerHTML: ''
});
input = $('input', this.controls);
- $.on(input, 'change', QuoteThreading.toggle);
+ $.on(input, 'change', this.toggle);
$.event('AddMenuEntry', {
type: 'header',
el: this.controls,
order: 98
});
- $.on(d, '4chanXInitFinished', this.setup);
+ if (!Conf['Unread Count']) {
+ $.on(d, '4chanXInitFinished', this.setup);
+ }
return Post.callbacks.push({
name: 'Quote Threading',
cb: this.node
});
},
setup: function() {
- var ID, post, posts;
+ var ID, post, _ref;
$.off(d, '4chanXInitFinished', QuoteThreading.setup);
- posts = g.posts;
- for (ID in posts) {
- post = posts[ID];
+ _ref = g.posts;
+ for (ID in _ref) {
+ post = _ref[ID];
if (post.cb) {
- post.cb.call(post);
+ post.cb();
}
}
return QuoteThreading.hasRun = true;
},
node: function() {
- var ID, fullID, keys, len, post, posts, qid, quote, quotes, uniq, _i, _len;
- if (this.isClone || !QuoteThreading.enabled || this.thread.OP === this) {
- return;
- }
- quotes = this.quotes, ID = this.ID, fullID = this.fullID;
+ var keys, len, post, posts, quote, _i, _len, _ref;
posts = g.posts;
- if (!(post = posts[fullID]) || post.isHidden) {
+ if (this.isClone || !QuoteThreading.enabled) {
return;
}
- uniq = {};
- len = ("" + g.BOARD).length + 1;
- for (_i = 0, _len = quotes.length; _i < _len; _i++) {
- quote = quotes[_i];
- qid = quote;
- if (!(qid.slice(len) < ID)) {
- continue;
- }
- if (qid in posts) {
- uniq[qid.slice(len)] = true;
+ if (Conf['Unread Count']) {
+ Unread.posts.push(this);
+ }
+ if (this.thread.OP === this || !(post = posts[this.fullID]) || post.isHidden) {
+ return;
+ }
+ keys = [];
+ len = g.BOARD.ID.length + 1;
+ _ref = this.quotes;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ quote = _ref[_i];
+ if ((quote.slice(len) < this.ID) && quote in posts) {
+ keys.push(quote);
}
}
- keys = Object.keys(uniq);
if (keys.length !== 1) {
return;
}
- this.threaded = "" + g.BOARD + "." + keys[0];
+ this.threaded = keys[0];
return this.cb = QuoteThreading.nodeinsert;
},
nodeinsert: function() {
- var bottom, height, qpost, qroot, threadContainer, top, _ref;
- qpost = g.posts[this.threaded];
- delete this.threaded;
- delete this.cb;
- if (this.thread.OP === qpost) {
+ var ID, bottom, height, post, posts, root, threadContainer, top, _ref;
+ post = g.posts[this.threaded];
+ posts = Unread.posts;
+ if (this.thread.OP === post) {
return false;
}
if (QuoteThreading.hasRun) {
height = doc.clientHeight;
- _ref = qpost.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
- if (!(__indexOf.call(Unread.posts, qpost) >= 0 || ((bottom < height) && (top > 0)))) {
+ _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
+ if (!((posts != null ? posts[post.ID] : void 0) || ((bottom < height) && (top > 0)))) {
return false;
}
}
- qroot = qpost.nodes.root;
- if (!$.hasClass(qroot, 'threadOP')) {
- $.addClass(qroot, 'threadOP');
+ root = post.nodes.root;
+ if (!$.hasClass(root, 'threadOP')) {
+ $.addClass(root, 'threadOP');
threadContainer = $.el('div', {
className: 'threadContainer'
});
- $.after(qroot, threadContainer);
+ $.after(root, threadContainer);
} else {
- threadContainer = qroot.nextSibling;
+ threadContainer = root.nextSibling;
+ post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer));
}
$.add(threadContainer, this.nodes.root);
+ if (!Conf['Unread Count']) {
+ return true;
+ }
+ if (posts[post.ID]) {
+ posts.after(post, this);
+ return true;
+ }
+ if ((ID = posts.closest(post.ID)) !== -1) {
+ posts.after(posts[ID], this);
+ } else {
+ posts.prepend(this);
+ }
return true;
},
toggle: function() {
- var container, containers, node, post, replies, reply, thread, _i, _j, _k, _len, _len1, _len2, _ref;
+ var container, containers, post, replies, reply, thread, _i, _j, _k, _len, _len1, _len2, _ref;
+ if (Conf['Unread Count']) {
+ Unread.posts = new RandomAccessList;
+ Unread.ready();
+ }
thread = $('.thread');
replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
- QuoteThreading.enabled = this.checked;
- if (this.checked) {
+ if (QuoteThreading.enabled = this.checked) {
QuoteThreading.hasRun = false;
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
- QuoteThreading.node.call(node = Get.postFromRoot(reply));
- if (node.cb) {
- node.cb();
+ post = Get.postFromRoot(reply);
+ if (post.cb) {
+ post.cb();
}
}
QuoteThreading.hasRun = true;
} else {
replies.sort(function(a, b) {
- var aID, bID;
- aID = Number(a.id.slice(2));
- bID = Number(b.id.slice(2));
- return aID - bID;
+ return Number(a.id.slice(2)) - Number(b.id.slice(2));
});
$.add(thread, replies);
containers = $$('.threadContainer', thread);
@@ -7241,12 +7346,15 @@
$.rmClass(post, 'threadOP');
}
}
- return Unread.update(true);
+ if (Conf['Unread Count']) {
+ return Unread.read();
+ }
},
kb: function() {
var control;
control = $.id('threadingControl');
- return control.click();
+ control.checked = !control.checked;
+ return QuoteThreading.toggle.call(control);
}
};
@@ -11168,7 +11276,7 @@
}
root = post.nodes.root;
if (post.cb) {
- if (!post.cb.call(post)) {
+ if (!post.cb()) {
$.add(ThreadUpdater.root, root);
}
} else {
@@ -11698,8 +11806,19 @@
this.hr = $.el('hr', {
id: 'unread-line'
});
- this.posts = [];
+ this.posts = new RandomAccessList;
this.postsQuotingYou = [];
+ this.qr = QR.db ? function(post) {
+ var data;
+ data = {
+ boardID: post.board.ID,
+ threadID: post.thread.ID,
+ postID: post.ID
+ };
+ return (QR.db.get(data) ? true : false);
+ } : function() {
+ return false;
+ };
return Thread.callbacks.push({
name: 'Unread',
cb: this.node
@@ -11732,17 +11851,19 @@
}
}
Unread.addPosts(posts);
- return Unread.scroll();
+ if (Conf['Quote Threading']) {
+ QuoteThreading.setup();
+ }
+ if (Conf['Scroll to Last Read Post']) {
+ return Unread.scroll();
+ }
},
scroll: function() {
var down, hash, post, posts, root;
- if (!Conf['Scroll to Last Read Post']) {
- return;
- }
if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return;
}
- if (post = Unread.posts[0]) {
+ if (post = Unread.posts.first) {
while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root)) {
if (!(post = Get.postFromRoot(root)).isHidden) {
break;
@@ -11761,7 +11882,7 @@
}
},
sync: function() {
- var lastReadPost;
+ var ID, lastReadPost, post;
lastReadPost = Unread.db.get({
boardID: Unread.thread.board.ID,
threadID: Unread.thread.ID,
@@ -11771,7 +11892,14 @@
return;
}
Unread.lastReadPost = lastReadPost;
- Unread.readArray(Unread.posts);
+ post = Unread.posts.first;
+ while (post) {
+ if ((ID = post.ID, post) > Unread.lastReadPost) {
+ break;
+ }
+ post = post.next;
+ Unread.posts.rm(ID);
+ }
Unread.readArray(Unread.postsQuotingYou);
if (Conf['Unread Line']) {
Unread.setLine();
@@ -11779,28 +11907,20 @@
return Unread.update();
},
addPosts: function(posts) {
- var ID, data, post, _i, _len, _ref;
+ var ID, post, _i, _len, _ref;
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
ID = post.ID;
- if (ID <= Unread.lastReadPost || post.isHidden) {
+ if (ID <= Unread.lastReadPost || post.isHidden || Unread.qr(post)) {
continue;
}
- if (QR.db) {
- data = {
- boardID: post.board.ID,
- threadID: post.thread.ID,
- postID: post.ID
- };
- if (QR.db.get(data)) {
- continue;
- }
+ if (!(post.prev || post.next)) {
+ Unread.posts.push(post);
}
- Unread.posts.push(post);
Unread.addPostQuotingYou(post);
}
if (Conf['Unread Line']) {
- Unread.setLine((_ref = Unread.posts[0], __indexOf.call(posts, _ref) >= 0));
+ Unread.setLine((_ref = Unread.posts.first, __indexOf.call(posts, _ref) >= 0));
}
Unread.read();
return Unread.update();
@@ -11849,15 +11969,16 @@
}
},
readSinglePost: function(post) {
- var i;
- if ((i = Unread.posts.indexOf(post)) === -1) {
+ var ID, i;
+ ID = post.ID;
+ if (!Unread.posts[ID]) {
return;
}
- Unread.posts.splice(i, 1);
- if (i === 0) {
- Unread.lastReadPost = post.ID;
+ if (post === Unread.posts.first) {
+ Unread.lastReadPost = ID;
Unread.saveLastReadPost();
}
+ Unread.posts.rm(ID);
if ((i = Unread.postsQuotingYou.indexOf(post)) !== -1) {
Unread.postsQuotingYou.splice(i, 1);
}
@@ -11873,35 +11994,22 @@
}
return arr.splice(0, i);
},
- read: $.debounce(50, function(e) {
- var ID, height, i, post, posts;
+ read: $.debounce(100, function(e) {
+ var ID, height, post, posts;
if (d.hidden || !Unread.posts.length) {
return;
}
height = doc.clientHeight;
posts = Unread.posts;
- i = 0;
- while (post = posts[i]) {
- if (Header.getBottomOf(post.nodes.root) > -1) {
- ID = post.ID;
- if (Conf['Mark Quotes of You']) {
- if (post.info.yours) {
- QuoteYou.lastRead = post.nodes.root;
- }
- }
- if (Conf['Quote Threading']) {
- posts.splice(i, 1);
- continue;
- }
- } else {
- if (!Conf['Quote Threading']) {
- break;
- }
+ while (post = posts.first) {
+ if (!(Header.getBottomOf(post.nodes.root) > -1)) {
+ break;
+ }
+ ID = post.ID;
+ posts.rm(ID);
+ if (Conf['Mark Quotes of You'] && post.info.yours) {
+ QuoteYou.lastRead = post.nodes.root;
}
- i++;
- }
- if (i && !Conf['Quote Threading']) {
- posts.splice(0, i);
}
if (!ID) {
return;
@@ -11930,7 +12038,7 @@
if (!(d.hidden || force === true)) {
return;
}
- if (!(post = Unread.posts[0])) {
+ if (!(post = Unread.posts.first)) {
return $.rm(Unread.hr);
}
if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root)) {
diff --git a/builds/crx/script.js b/builds/crx/script.js
index b037fa137..0783e0426 100644
--- a/builds/crx/script.js
+++ b/builds/crx/script.js
@@ -1,6 +1,6 @@
// Generated by CoffeeScript
/*
-* appchan x - Version 2.7.5 - 2014-01-04
+* appchan x - Version 2.7.5 - 2014-01-06
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@@ -88,7 +88,7 @@
'use strict';
(function() {
- var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, CatalogLinks, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
+ var $, $$, Anonymize, ArchiveLink, AutoGIF, Banner, Board, Build, CatalogLinks, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, Emoji, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, InfiniScroll, JSColor, Keybinds, Linkify, Main, MascotTools, Mascots, Menu, Nav, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, c, d, doc, editMascot, editTheme, g, userNavigation,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__slice = [].slice,
__hasProp = {}.hasOwnProperty,
@@ -3832,6 +3832,99 @@
})();
+ RandomAccessList = (function() {
+ function RandomAccessList() {
+ this.length = 0;
+ }
+
+ RandomAccessList.prototype.push = function(item) {
+ var ID, last;
+ ID = item.ID;
+ if (this[ID]) {
+ return;
+ }
+ last = this.last;
+ item.prev = last;
+ this[ID] = item;
+ this.last = last ? last.next = item : this.first = item;
+ return this.length++;
+ };
+
+ RandomAccessList.prototype.after = function(root, item) {
+ var next;
+ if (item.prev === root) {
+ return;
+ }
+ this.rmi(item);
+ next = root.next;
+ root.next = item;
+ item.prev = root;
+ item.next = next;
+ return next.prev = item;
+ };
+
+ RandomAccessList.prototype.prepend = function(item) {
+ var first;
+ first = this.first;
+ if (item === first || !this[item.ID]) {
+ return;
+ }
+ this.rmi(item);
+ item.next = first;
+ first.prev = item;
+ this.first = item;
+ return delete item.prev;
+ };
+
+ RandomAccessList.prototype.shift = function() {
+ return this.rm(this.first.ID);
+ };
+
+ RandomAccessList.prototype.rm = function(ID) {
+ var item;
+ item = this[ID];
+ if (!item) {
+ return;
+ }
+ delete this[ID];
+ this.length--;
+ this.rmi(item);
+ delete item.next;
+ return delete item.prev;
+ };
+
+ RandomAccessList.prototype.rmi = function(item) {
+ var next, prev;
+ prev = item.prev, next = item.next;
+ if (prev) {
+ prev.next = next;
+ } else {
+ this.first = next;
+ }
+ if (next) {
+ return next.prev = prev;
+ } else {
+ return this.last = prev;
+ }
+ };
+
+ RandomAccessList.prototype.closest = function(ID) {
+ var item, prev;
+ item = this.first;
+ while (item) {
+ if (item.ID > ID) {
+ prev = item.prev;
+ break;
+ }
+ item = item.next;
+ }
+ return (prev ? prev.ID : -1);
+ };
+
+ return RandomAccessList;
+
+ })();
+
Polyfill = {
init: function() {
this.notificationPermission();
@@ -7130,108 +7223,120 @@
innerHTML: ''
});
input = $('input', this.controls);
- $.on(input, 'change', QuoteThreading.toggle);
+ $.on(input, 'change', this.toggle);
$.event('AddMenuEntry', {
type: 'header',
el: this.controls,
order: 98
});
- $.on(d, '4chanXInitFinished', this.setup);
+ if (!Conf['Unread Count']) {
+ $.on(d, '4chanXInitFinished', this.setup);
+ }
return Post.callbacks.push({
name: 'Quote Threading',
cb: this.node
});
},
setup: function() {
- var ID, post, posts;
+ var ID, post, _ref;
$.off(d, '4chanXInitFinished', QuoteThreading.setup);
- posts = g.posts;
- for (ID in posts) {
- post = posts[ID];
+ _ref = g.posts;
+ for (ID in _ref) {
+ post = _ref[ID];
if (post.cb) {
- post.cb.call(post);
+ post.cb();
}
}
return QuoteThreading.hasRun = true;
},
node: function() {
- var ID, fullID, keys, len, post, posts, qid, quote, quotes, uniq, _i, _len;
- if (this.isClone || !QuoteThreading.enabled || this.thread.OP === this) {
- return;
- }
- quotes = this.quotes, ID = this.ID, fullID = this.fullID;
+ var keys, len, post, posts, quote, _i, _len, _ref;
posts = g.posts;
- if (!(post = posts[fullID]) || post.isHidden) {
+ if (this.isClone || !QuoteThreading.enabled) {
return;
}
- uniq = {};
- len = ("" + g.BOARD).length + 1;
- for (_i = 0, _len = quotes.length; _i < _len; _i++) {
- quote = quotes[_i];
- qid = quote;
- if (!(qid.slice(len) < ID)) {
- continue;
- }
- if (qid in posts) {
- uniq[qid.slice(len)] = true;
+ if (Conf['Unread Count']) {
+ Unread.posts.push(this);
+ }
+ if (this.thread.OP === this || !(post = posts[this.fullID]) || post.isHidden) {
+ return;
+ }
+ keys = [];
+ len = g.BOARD.ID.length + 1;
+ _ref = this.quotes;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ quote = _ref[_i];
+ if ((quote.slice(len) < this.ID) && quote in posts) {
+ keys.push(quote);
}
}
- keys = Object.keys(uniq);
if (keys.length !== 1) {
return;
}
- this.threaded = "" + g.BOARD + "." + keys[0];
+ this.threaded = keys[0];
return this.cb = QuoteThreading.nodeinsert;
},
nodeinsert: function() {
- var bottom, height, qpost, qroot, threadContainer, top, _ref;
- qpost = g.posts[this.threaded];
- delete this.threaded;
- delete this.cb;
- if (this.thread.OP === qpost) {
+ var ID, bottom, height, post, posts, root, threadContainer, top, _ref;
+ post = g.posts[this.threaded];
+ posts = Unread.posts;
+ if (this.thread.OP === post) {
return false;
}
if (QuoteThreading.hasRun) {
height = doc.clientHeight;
- _ref = qpost.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
- if (!(__indexOf.call(Unread.posts, qpost) >= 0 || ((bottom < height) && (top > 0)))) {
+ _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
+ if (!((posts != null ? posts[post.ID] : void 0) || ((bottom < height) && (top > 0)))) {
return false;
}
}
- qroot = qpost.nodes.root;
- if (!$.hasClass(qroot, 'threadOP')) {
- $.addClass(qroot, 'threadOP');
+ root = post.nodes.root;
+ if (!$.hasClass(root, 'threadOP')) {
+ $.addClass(root, 'threadOP');
threadContainer = $.el('div', {
className: 'threadContainer'
});
- $.after(qroot, threadContainer);
+ $.after(root, threadContainer);
} else {
- threadContainer = qroot.nextSibling;
+ threadContainer = root.nextSibling;
+ post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer));
}
$.add(threadContainer, this.nodes.root);
+ if (!Conf['Unread Count']) {
+ return true;
+ }
+ if (posts[post.ID]) {
+ posts.after(post, this);
+ return true;
+ }
+ if ((ID = posts.closest(post.ID)) !== -1) {
+ posts.after(posts[ID], this);
+ } else {
+ posts.prepend(this);
+ }
return true;
},
toggle: function() {
- var container, containers, node, post, replies, reply, thread, _i, _j, _k, _len, _len1, _len2, _ref;
+ var container, containers, post, replies, reply, thread, _i, _j, _k, _len, _len1, _len2, _ref;
+ if (Conf['Unread Count']) {
+ Unread.posts = new RandomAccessList;
+ Unread.ready();
+ }
thread = $('.thread');
replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
- QuoteThreading.enabled = this.checked;
- if (this.checked) {
+ if (QuoteThreading.enabled = this.checked) {
QuoteThreading.hasRun = false;
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
- QuoteThreading.node.call(node = Get.postFromRoot(reply));
- if (node.cb) {
- node.cb();
+ post = Get.postFromRoot(reply);
+ if (post.cb) {
+ post.cb();
}
}
QuoteThreading.hasRun = true;
} else {
replies.sort(function(a, b) {
- var aID, bID;
- aID = Number(a.id.slice(2));
- bID = Number(b.id.slice(2));
- return aID - bID;
+ return Number(a.id.slice(2)) - Number(b.id.slice(2));
});
$.add(thread, replies);
containers = $$('.threadContainer', thread);
@@ -7245,12 +7350,15 @@
$.rmClass(post, 'threadOP');
}
}
- return Unread.update(true);
+ if (Conf['Unread Count']) {
+ return Unread.read();
+ }
},
kb: function() {
var control;
control = $.id('threadingControl');
- return control.click();
+ control.checked = !control.checked;
+ return QuoteThreading.toggle.call(control);
}
};
@@ -11152,7 +11260,7 @@
}
root = post.nodes.root;
if (post.cb) {
- if (!post.cb.call(post)) {
+ if (!post.cb()) {
$.add(ThreadUpdater.root, root);
}
} else {
@@ -11682,8 +11790,19 @@
this.hr = $.el('hr', {
id: 'unread-line'
});
- this.posts = [];
+ this.posts = new RandomAccessList;
this.postsQuotingYou = [];
+ this.qr = QR.db ? function(post) {
+ var data;
+ data = {
+ boardID: post.board.ID,
+ threadID: post.thread.ID,
+ postID: post.ID
+ };
+ return (QR.db.get(data) ? true : false);
+ } : function() {
+ return false;
+ };
return Thread.callbacks.push({
name: 'Unread',
cb: this.node
@@ -11716,17 +11835,19 @@
}
}
Unread.addPosts(posts);
- return Unread.scroll();
+ if (Conf['Quote Threading']) {
+ QuoteThreading.setup();
+ }
+ if (Conf['Scroll to Last Read Post']) {
+ return Unread.scroll();
+ }
},
scroll: function() {
var down, hash, post, posts, root;
- if (!Conf['Scroll to Last Read Post']) {
- return;
- }
if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return;
}
- if (post = Unread.posts[0]) {
+ if (post = Unread.posts.first) {
while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root)) {
if (!(post = Get.postFromRoot(root)).isHidden) {
break;
@@ -11745,7 +11866,7 @@
}
},
sync: function() {
- var lastReadPost;
+ var ID, lastReadPost, post;
lastReadPost = Unread.db.get({
boardID: Unread.thread.board.ID,
threadID: Unread.thread.ID,
@@ -11755,7 +11876,14 @@
return;
}
Unread.lastReadPost = lastReadPost;
- Unread.readArray(Unread.posts);
+ post = Unread.posts.first;
+ while (post) {
+ if ((ID = post.ID, post) > Unread.lastReadPost) {
+ break;
+ }
+ post = post.next;
+ Unread.posts.rm(ID);
+ }
Unread.readArray(Unread.postsQuotingYou);
if (Conf['Unread Line']) {
Unread.setLine();
@@ -11763,28 +11891,20 @@
return Unread.update();
},
addPosts: function(posts) {
- var ID, data, post, _i, _len, _ref;
+ var ID, post, _i, _len, _ref;
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
ID = post.ID;
- if (ID <= Unread.lastReadPost || post.isHidden) {
+ if (ID <= Unread.lastReadPost || post.isHidden || Unread.qr(post)) {
continue;
}
- if (QR.db) {
- data = {
- boardID: post.board.ID,
- threadID: post.thread.ID,
- postID: post.ID
- };
- if (QR.db.get(data)) {
- continue;
- }
+ if (!(post.prev || post.next)) {
+ Unread.posts.push(post);
}
- Unread.posts.push(post);
Unread.addPostQuotingYou(post);
}
if (Conf['Unread Line']) {
- Unread.setLine((_ref = Unread.posts[0], __indexOf.call(posts, _ref) >= 0));
+ Unread.setLine((_ref = Unread.posts.first, __indexOf.call(posts, _ref) >= 0));
}
Unread.read();
return Unread.update();
@@ -11833,15 +11953,16 @@
}
},
readSinglePost: function(post) {
- var i;
- if ((i = Unread.posts.indexOf(post)) === -1) {
+ var ID, i;
+ ID = post.ID;
+ if (!Unread.posts[ID]) {
return;
}
- Unread.posts.splice(i, 1);
- if (i === 0) {
- Unread.lastReadPost = post.ID;
+ if (post === Unread.posts.first) {
+ Unread.lastReadPost = ID;
Unread.saveLastReadPost();
}
+ Unread.posts.rm(ID);
if ((i = Unread.postsQuotingYou.indexOf(post)) !== -1) {
Unread.postsQuotingYou.splice(i, 1);
}
@@ -11857,35 +11978,22 @@
}
return arr.splice(0, i);
},
- read: $.debounce(50, function(e) {
- var ID, height, i, post, posts;
+ read: $.debounce(100, function(e) {
+ var ID, height, post, posts;
if (d.hidden || !Unread.posts.length) {
return;
}
height = doc.clientHeight;
posts = Unread.posts;
- i = 0;
- while (post = posts[i]) {
- if (Header.getBottomOf(post.nodes.root) > -1) {
- ID = post.ID;
- if (Conf['Mark Quotes of You']) {
- if (post.info.yours) {
- QuoteYou.lastRead = post.nodes.root;
- }
- }
- if (Conf['Quote Threading']) {
- posts.splice(i, 1);
- continue;
- }
- } else {
- if (!Conf['Quote Threading']) {
- break;
- }
+ while (post = posts.first) {
+ if (!(Header.getBottomOf(post.nodes.root) > -1)) {
+ break;
+ }
+ ID = post.ID;
+ posts.rm(ID);
+ if (Conf['Mark Quotes of You'] && post.info.yours) {
+ QuoteYou.lastRead = post.nodes.root;
}
- i++;
- }
- if (i && !Conf['Quote Threading']) {
- posts.splice(0, i);
}
if (!ID) {
return;
@@ -11914,7 +12022,7 @@
if (!(d.hidden || force === true)) {
return;
}
- if (!(post = Unread.posts[0])) {
+ if (!(post = Unread.posts.first)) {
return $.rm(Unread.hr);
}
if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root)) {
diff --git a/src/General/lib/classes.coffee b/src/General/lib/classes.coffee
index 74697eec3..63c33645e 100755
--- a/src/General/lib/classes.coffee
+++ b/src/General/lib/classes.coffee
@@ -3,4 +3,5 @@
<%= grunt.file.read('src/General/lib/post.class') %>
<%= grunt.file.read('src/General/lib/clone.class') %>
<%= grunt.file.read('src/General/lib/databoard.class') %>
-<%= grunt.file.read('src/General/lib/notice.class') %>
\ No newline at end of file
+<%= grunt.file.read('src/General/lib/notice.class') %>
+<%= grunt.file.read('src/General/lib/randomaccesslist.class') %>
\ No newline at end of file
diff --git a/src/General/lib/randomaccesslist.class b/src/General/lib/randomaccesslist.class
new file mode 100644
index 000000000..98c08a670
--- /dev/null
+++ b/src/General/lib/randomaccesslist.class
@@ -0,0 +1,67 @@
+class RandomAccessList
+ constructor: ->
+ @length = 0
+
+ push: (item) ->
+ {ID} = item
+ return if @[ID]
+ {last} = @
+ item.prev = last
+ @[ID] = item
+ @last = if last
+ last.next = item
+ else
+ @first = item
+ @length++
+
+ after: (root, item) ->
+ return if item.prev is root
+
+ @rmi item
+
+ {next} = root
+ root.next = item
+ item.prev = root
+ item.next = next
+ next.prev = item
+
+ prepend: (item) ->
+ {first} = @
+ return if item is first or not @[item.ID]
+ @rmi item
+ item.next = first
+ first.prev = item
+ @first = item
+ delete item.prev
+
+ shift: ->
+ @rm @first.ID
+
+ rm: (ID) ->
+ item = @[ID]
+ return unless item
+ delete @[ID]
+ @length--
+ @rmi item
+ delete item.next
+ delete item.prev
+
+ rmi: (item) ->
+ {prev, next} = item
+ if prev
+ prev.next = next
+ else
+ @first = next
+ if next
+ next.prev = prev
+ else
+ @last = prev
+
+ closest: (ID) ->
+ item = @first
+ while item
+ if item.ID > ID
+ {prev} = item
+ break
+ item = item.next
+ return (if prev then prev.ID else -1)
\ No newline at end of file
diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee
index 13fb2edd8..abbadd3cd 100755
--- a/src/Monitoring/ThreadUpdater.coffee
+++ b/src/Monitoring/ThreadUpdater.coffee
@@ -296,7 +296,7 @@ ThreadUpdater =
continue unless posts.hasOwnProperty key
root = post.nodes.root
if post.cb
- unless post.cb.call post
+ unless post.cb()
$.add ThreadUpdater.root, root
else
$.add ThreadUpdater.root, root
diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee
index 385383639..02c50faf5 100755
--- a/src/Monitoring/Unread.coffee
+++ b/src/Monitoring/Unread.coffee
@@ -5,9 +5,19 @@ Unread =
@db = new DataBoard 'lastReadPosts', @sync
@hr = $.el 'hr',
id: 'unread-line'
- @posts = []
+ @posts = new RandomAccessList
@postsQuotingYou = []
+ @qr = if QR.db
+ (post) ->
+ data =
+ boardID: post.board.ID
+ threadID: post.thread.ID
+ postID: post.ID
+ return (if QR.db.get data then true else false)
+ else ->
+ return false
+
Thread.callbacks.push
name: 'Unread'
cb: @node
@@ -27,16 +37,15 @@ Unread =
ready: ->
$.off d, '4chanXInitFinished', Unread.ready
posts = []
- for ID, post of Unread.thread.posts
- posts.push post if post.isReply
+ posts.push post for ID, post of Unread.thread.posts when post.isReply
Unread.addPosts posts
- Unread.scroll()
+ QuoteThreading.setup() if Conf['Quote Threading']
+ Unread.scroll() if Conf['Scroll to Last Read Post']
scroll: ->
- return unless Conf['Scroll to Last Read Post']
# Let the header's onload callback handle it.
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
- if post = Unread.posts[0]
+ if post = Unread.posts.first
# Scroll to a non-hidden, non-OP post that's before the first unread post.
while root = $.x 'preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root
break unless (post = Get.postFromRoot root).isHidden
@@ -46,7 +55,7 @@ Unread =
# Scroll to the last read post.
posts = Object.keys Unread.thread.posts
{root} = Unread.thread.posts[posts[posts.length - 1]].nodes
-
+
# Scroll to the target unless we scrolled past it.
Header.scrollTo root, down if Header.getBottomOf(root) < 0
@@ -57,7 +66,13 @@ Unread =
defaultValue: 0
return unless Unread.lastReadPost < lastReadPost
Unread.lastReadPost = lastReadPost
- Unread.readArray Unread.posts
+
+ post = Unread.posts.first
+ while post
+ break if ({ID} = post) > Unread.lastReadPost
+ post = post.next
+ Unread.posts.rm ID
+
Unread.readArray Unread.postsQuotingYou
Unread.setLine() if Conf['Unread Line']
Unread.update()
@@ -65,19 +80,12 @@ Unread =
addPosts: (posts) ->
for post in posts
{ID} = post
- if ID <= Unread.lastReadPost or post.isHidden
- continue
- if QR.db
- data =
- boardID: post.board.ID
- threadID: post.thread.ID
- postID: post.ID
- continue if QR.db.get data
- Unread.posts.push post
+ continue if ID <= Unread.lastReadPost or post.isHidden or Unread.qr post
+ Unread.posts.push post unless post.prev or post.next
Unread.addPostQuotingYou post
if Conf['Unread Line']
# Force line on visible threads if there were no unread posts previously.
- Unread.setLine Unread.posts[0] in posts
+ Unread.setLine Unread.posts.first in posts
Unread.read()
Unread.update()
@@ -112,11 +120,12 @@ Unread =
Unread.addPosts e.detail.newPosts
readSinglePost: (post) ->
- return if (i = Unread.posts.indexOf post) is -1
- Unread.posts.splice i, 1
- if i is 0
- Unread.lastReadPost = post.ID
+ {ID} = post
+ return unless Unread.posts[ID]
+ if post is Unread.posts.first
+ Unread.lastReadPost = ID
Unread.saveLastReadPost()
+ Unread.posts.rm ID
if (i = Unread.postsQuotingYou.indexOf post) isnt -1
Unread.postsQuotingYou.splice i, 1
Unread.update()
@@ -126,28 +135,18 @@ Unread =
break if post.ID > Unread.lastReadPost
arr.splice 0, i
- read: $.debounce 50, (e) ->
+ read: $.debounce 100, (e) ->
return if d.hidden or !Unread.posts.length
height = doc.clientHeight
- {posts} = Unread
- i = 0
- while post = posts[i]
- if Header.getBottomOf(post.nodes.root) > -1 # post is not completely read
- {ID} = post
- if Conf['Mark Quotes of You']
- if post.info.yours
- QuoteYou.lastRead = post.nodes.root
- if Conf['Quote Threading']
- posts.splice i, 1
- continue
- else
- unless Conf['Quote Threading']
- break
- i++
-
- if i and !Conf['Quote Threading']
- posts.splice 0, i
+ {posts} = Unread
+ while post = posts.first
+ break unless Header.getBottomOf(post.nodes.root) > -1 # post is not completely read
+ {ID} = post
+ posts.rm ID
+
+ if Conf['Mark Quotes of You'] and post.info.yours
+ QuoteYou.lastRead = post.nodes.root
return unless ID
@@ -159,13 +158,13 @@ Unread =
saveLastReadPost: $.debounce 2 * $.SECOND, ->
return if Unread.thread.isDead
Unread.db.set
- boardID: Unread.thread.board.ID
+ boardID: Unread.thread.board.ID
threadID: Unread.thread.ID
val: Unread.lastReadPost
setLine: (force) ->
return unless d.hidden or force is true
- return $.rm Unread.hr unless post = Unread.posts[0]
+ return $.rm Unread.hr unless post = Unread.posts.first
if $.x 'preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root # not the first reply
$.before post.nodes.root, Unread.hr
@@ -173,7 +172,7 @@ Unread =
count = Unread.posts.length
if Conf['Unread Count']
- d.title = "#{if Conf['Quoted Title'] and Unread.postsQuotingYou.length then '(!) ' else ''}#{if count or !Conf['Hide Unread Count at (0)'] then "(#{count}) " else ''}#{if g.DEAD then "/#{g.BOARD}/ - 404" else "#{Unread.title}"}"
+ d.title = "#{if Conf['Quoted Title'] and Unread.postsQuotingYou.length then '(!) ' else ''}#{if count or !Conf['Hide Unread Count at (0)'] then "(#{count}) " else ''}#{if g.DEAD then "/#{g.BOARD}/ - 404" else "#{Unread.title}"}"
<% if (type === 'crx') { %>
# XXX Chrome bug where it doesn't always update the tab title.
# crbug.com/124381
diff --git a/src/Quotelinks/QuoteThreading.coffee b/src/Quotelinks/QuoteThreading.coffee
index 3287c9a4f..ddc975dd0 100755
--- a/src/Quotelinks/QuoteThreading.coffee
+++ b/src/Quotelinks/QuoteThreading.coffee
@@ -11,14 +11,14 @@ QuoteThreading =
innerHTML: ''
input = $ 'input', @controls
- $.on input, 'change', QuoteThreading.toggle
+ $.on input, 'change', @toggle
$.event 'AddMenuEntry',
type: 'header'
el: @controls
order: 98
- $.on d, '4chanXInitFinished', @setup
+ $.on d, '4chanXInitFinished', @setup unless Conf['Unread Count']
Post.callbacks.push
name: 'Quote Threading'
@@ -26,83 +26,91 @@ QuoteThreading =
setup: ->
$.off d, '4chanXInitFinished', QuoteThreading.setup
- {posts} = g
-
- for ID, post of posts
- if post.cb
- post.cb.call post
+ post.cb() for ID, post of g.posts when post.cb
QuoteThreading.hasRun = true
node: ->
- return if @isClone or not QuoteThreading.enabled or @thread.OP is @
-
- {quotes, ID, fullID} = @
{posts} = g
- return if !(post = posts[fullID]) or post.isHidden # Filtered
+ return if @isClone or not QuoteThreading.enabled
+ Unread.posts.push @ if Conf['Unread Count']
- uniq = {}
- len = "#{g.BOARD}".length + 1
- for quote in quotes
- qid = quote
- continue unless qid[len..] < ID
- if qid of posts
- uniq[qid[len..]] = true
+ return if @thread.OP is @ or !(post = posts[@fullID]) or post.isHidden # Filtered
+
+ keys = []
+ len = g.BOARD.ID.length + 1
+ keys.push quote for quote in @quotes when (quote[len..] < @ID) and quote of posts
- keys = Object.keys uniq
return unless keys.length is 1
- @threaded = "#{g.BOARD}.#{keys[0]}"
+ @threaded = keys[0]
@cb = QuoteThreading.nodeinsert
nodeinsert: ->
- qpost = g.posts[@threaded]
+ post = g.posts[@threaded]
+ {posts} = Unread
- delete @threaded
- delete @cb
-
- return false if @thread.OP is qpost
+ return false if @thread.OP is post
if QuoteThreading.hasRun
height = doc.clientHeight
- {bottom, top} = qpost.nodes.root.getBoundingClientRect()
+ {bottom, top} = post.nodes.root.getBoundingClientRect()
# Post is unread or is fully visible.
- return false unless qpost in Unread.posts or ((bottom < height) and (top > 0))
+ return false unless posts?[post.ID] or ((bottom < height) and (top > 0))
- qroot = qpost.nodes.root
- unless $.hasClass qroot, 'threadOP'
- $.addClass qroot, 'threadOP'
+ {root} = post.nodes
+ unless $.hasClass root, 'threadOP'
+ $.addClass root, 'threadOP'
threadContainer = $.el 'div',
className: 'threadContainer'
- $.after qroot, threadContainer
+ $.after root, threadContainer
else
- threadContainer = qroot.nextSibling
+ threadContainer = root.nextSibling
+ post = Get.postFromRoot $.x 'descendant::div[contains(@class,"postContainer")][last()]', threadContainer
$.add threadContainer, @nodes.root
+
+ return true unless Conf['Unread Count']
+
+ if posts[post.ID]
+ posts.after post, @
+ return true
+
+ if (ID = posts.closest post.ID) isnt -1
+ posts.after posts[ID], @
+ else
+ posts.prepend @
+
return true
toggle: ->
+ if Conf['Unread Count']
+ Unread.posts = new RandomAccessList
+ Unread.ready()
+
thread = $ '.thread'
replies = $$ '.thread > .replyContainer, .threadContainer > .replyContainer', thread
- QuoteThreading.enabled = @checked
- if @checked
+
+ if QuoteThreading.enabled = @checked
QuoteThreading.hasRun = false
for reply in replies
- QuoteThreading.node.call node = Get.postFromRoot reply
- node.cb() if node.cb
+ post = Get.postFromRoot reply
+ # QuoteThreading calculates whether or not posts should be threaded based on content
+ # and then threads them based on thread context, so regardless of whether or not it
+ # actually threads them all eligible posts WILL have a cb. Magic.
+ post.cb() if post.cb
QuoteThreading.hasRun = true
+
else
- replies.sort (a, b) ->
- aID = Number a.id[2..]
- bID = Number b.id[2..]
- aID - bID
+ replies.sort (a, b) -> Number(a.id[2..]) - Number(b.id[2..])
$.add thread, replies
containers = $$ '.threadContainer', thread
$.rm container for container in containers
$.rmClass post, 'threadOP' for post in $$ '.threadOP'
- Unread.update true
+ Unread.read() if Conf['Unread Count']
kb: ->
control = $.id 'threadingControl'
- control.click()
\ No newline at end of file
+ control.checked = not control.checked
+ QuoteThreading.toggle.call control
\ No newline at end of file