Merge branch 'v3'

Conflicts:
	LICENSE
	builds/appchan-x.user.js
	builds/crx/script.js
This commit is contained in:
Zixaphir 2014-01-06 11:11:12 -07:00
commit bde16c1a64
8 changed files with 584 additions and 293 deletions

View File

@ -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. * Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE * https://github.com/zixaphir/appchan-x/blob/master/LICENSE

View File

@ -22,7 +22,7 @@
// ==/UserScript== // ==/UserScript==
/* /*
* appchan x - Version 2.7.5 - 2014-01-04 * appchan x - Version 2.7.5 - 2014-01-06
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE * https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -110,7 +110,7 @@
'use strict'; 'use strict';
(function() { (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; }, __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, __slice = [].slice,
__hasProp = {}.hasOwnProperty, __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 = { Polyfill = {
init: function() {}, init: function() {},
notificationPermission: function() { notificationPermission: function() {
@ -7126,108 +7219,120 @@
innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>' innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>'
}); });
input = $('input', this.controls); input = $('input', this.controls);
$.on(input, 'change', QuoteThreading.toggle); $.on(input, 'change', this.toggle);
$.event('AddMenuEntry', { $.event('AddMenuEntry', {
type: 'header', type: 'header',
el: this.controls, el: this.controls,
order: 98 order: 98
}); });
if (!Conf['Unread Count']) {
$.on(d, '4chanXInitFinished', this.setup); $.on(d, '4chanXInitFinished', this.setup);
}
return Post.callbacks.push({ return Post.callbacks.push({
name: 'Quote Threading', name: 'Quote Threading',
cb: this.node cb: this.node
}); });
}, },
setup: function() { setup: function() {
var ID, post, posts; var ID, post, _ref;
$.off(d, '4chanXInitFinished', QuoteThreading.setup); $.off(d, '4chanXInitFinished', QuoteThreading.setup);
posts = g.posts; _ref = g.posts;
for (ID in posts) { for (ID in _ref) {
post = posts[ID]; post = _ref[ID];
if (post.cb) { if (post.cb) {
post.cb.call(post); post.cb();
} }
} }
return QuoteThreading.hasRun = true; return QuoteThreading.hasRun = true;
}, },
node: function() { node: function() {
var ID, fullID, keys, len, post, posts, qid, quote, quotes, uniq, _i, _len; var keys, len, post, posts, quote, _i, _len, _ref;
if (this.isClone || !QuoteThreading.enabled || this.thread.OP === this) {
return;
}
quotes = this.quotes, ID = this.ID, fullID = this.fullID;
posts = g.posts; posts = g.posts;
if (!(post = posts[fullID]) || post.isHidden) { if (this.isClone || !QuoteThreading.enabled) {
return; return;
} }
uniq = {}; if (Conf['Unread Count']) {
len = ("" + g.BOARD).length + 1; Unread.posts.push(this);
for (_i = 0, _len = quotes.length; _i < _len; _i++) {
quote = quotes[_i];
qid = quote;
if (!(qid.slice(len) < ID)) {
continue;
} }
if (qid in posts) { if (this.thread.OP === this || !(post = posts[this.fullID]) || post.isHidden) {
uniq[qid.slice(len)] = true; 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) { if (keys.length !== 1) {
return; return;
} }
this.threaded = "" + g.BOARD + "." + keys[0]; this.threaded = keys[0];
return this.cb = QuoteThreading.nodeinsert; return this.cb = QuoteThreading.nodeinsert;
}, },
nodeinsert: function() { nodeinsert: function() {
var bottom, height, qpost, qroot, threadContainer, top, _ref; var ID, bottom, height, post, posts, root, threadContainer, top, _ref;
qpost = g.posts[this.threaded]; post = g.posts[this.threaded];
delete this.threaded; posts = Unread.posts;
delete this.cb; if (this.thread.OP === post) {
if (this.thread.OP === qpost) {
return false; return false;
} }
if (QuoteThreading.hasRun) { if (QuoteThreading.hasRun) {
height = doc.clientHeight; height = doc.clientHeight;
_ref = qpost.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top; _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
if (!(__indexOf.call(Unread.posts, qpost) >= 0 || ((bottom < height) && (top > 0)))) { if (!((posts != null ? posts[post.ID] : void 0) || ((bottom < height) && (top > 0)))) {
return false; return false;
} }
} }
qroot = qpost.nodes.root; root = post.nodes.root;
if (!$.hasClass(qroot, 'threadOP')) { if (!$.hasClass(root, 'threadOP')) {
$.addClass(qroot, 'threadOP'); $.addClass(root, 'threadOP');
threadContainer = $.el('div', { threadContainer = $.el('div', {
className: 'threadContainer' className: 'threadContainer'
}); });
$.after(qroot, threadContainer); $.after(root, threadContainer);
} else { } else {
threadContainer = qroot.nextSibling; threadContainer = root.nextSibling;
post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer));
} }
$.add(threadContainer, this.nodes.root); $.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; return true;
}, },
toggle: function() { 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'); thread = $('.thread');
replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread); replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
QuoteThreading.enabled = this.checked; if (QuoteThreading.enabled = this.checked) {
if (this.checked) {
QuoteThreading.hasRun = false; QuoteThreading.hasRun = false;
for (_i = 0, _len = replies.length; _i < _len; _i++) { for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i]; reply = replies[_i];
QuoteThreading.node.call(node = Get.postFromRoot(reply)); post = Get.postFromRoot(reply);
if (node.cb) { if (post.cb) {
node.cb(); post.cb();
} }
} }
QuoteThreading.hasRun = true; QuoteThreading.hasRun = true;
} else { } else {
replies.sort(function(a, b) { replies.sort(function(a, b) {
var aID, bID; return Number(a.id.slice(2)) - Number(b.id.slice(2));
aID = Number(a.id.slice(2));
bID = Number(b.id.slice(2));
return aID - bID;
}); });
$.add(thread, replies); $.add(thread, replies);
containers = $$('.threadContainer', thread); containers = $$('.threadContainer', thread);
@ -7241,12 +7346,15 @@
$.rmClass(post, 'threadOP'); $.rmClass(post, 'threadOP');
} }
} }
return Unread.update(true); if (Conf['Unread Count']) {
return Unread.read();
}
}, },
kb: function() { kb: function() {
var control; var control;
control = $.id('threadingControl'); control = $.id('threadingControl');
return control.click(); control.checked = !control.checked;
return QuoteThreading.toggle.call(control);
} }
}; };
@ -11168,7 +11276,7 @@
} }
root = post.nodes.root; root = post.nodes.root;
if (post.cb) { if (post.cb) {
if (!post.cb.call(post)) { if (!post.cb()) {
$.add(ThreadUpdater.root, root); $.add(ThreadUpdater.root, root);
} }
} else { } else {
@ -11698,8 +11806,19 @@
this.hr = $.el('hr', { this.hr = $.el('hr', {
id: 'unread-line' id: 'unread-line'
}); });
this.posts = []; this.posts = new RandomAccessList;
this.postsQuotingYou = []; 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({ return Thread.callbacks.push({
name: 'Unread', name: 'Unread',
cb: this.node cb: this.node
@ -11732,17 +11851,19 @@
} }
} }
Unread.addPosts(posts); Unread.addPosts(posts);
if (Conf['Quote Threading']) {
QuoteThreading.setup();
}
if (Conf['Scroll to Last Read Post']) {
return Unread.scroll(); return Unread.scroll();
}
}, },
scroll: function() { scroll: function() {
var down, hash, post, posts, root; 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) { if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return; return;
} }
if (post = Unread.posts[0]) { if (post = Unread.posts.first) {
while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root)) { while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root)) {
if (!(post = Get.postFromRoot(root)).isHidden) { if (!(post = Get.postFromRoot(root)).isHidden) {
break; break;
@ -11761,7 +11882,7 @@
} }
}, },
sync: function() { sync: function() {
var lastReadPost; var ID, lastReadPost, post;
lastReadPost = Unread.db.get({ lastReadPost = Unread.db.get({
boardID: Unread.thread.board.ID, boardID: Unread.thread.board.ID,
threadID: Unread.thread.ID, threadID: Unread.thread.ID,
@ -11771,7 +11892,14 @@
return; return;
} }
Unread.lastReadPost = lastReadPost; 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); Unread.readArray(Unread.postsQuotingYou);
if (Conf['Unread Line']) { if (Conf['Unread Line']) {
Unread.setLine(); Unread.setLine();
@ -11779,28 +11907,20 @@
return Unread.update(); return Unread.update();
}, },
addPosts: function(posts) { 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++) { for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i]; post = posts[_i];
ID = post.ID; ID = post.ID;
if (ID <= Unread.lastReadPost || post.isHidden) { if (ID <= Unread.lastReadPost || post.isHidden || Unread.qr(post)) {
continue; continue;
} }
if (QR.db) { if (!(post.prev || post.next)) {
data = {
boardID: post.board.ID,
threadID: post.thread.ID,
postID: post.ID
};
if (QR.db.get(data)) {
continue;
}
}
Unread.posts.push(post); Unread.posts.push(post);
}
Unread.addPostQuotingYou(post); Unread.addPostQuotingYou(post);
} }
if (Conf['Unread Line']) { 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(); Unread.read();
return Unread.update(); return Unread.update();
@ -11849,15 +11969,16 @@
} }
}, },
readSinglePost: function(post) { readSinglePost: function(post) {
var i; var ID, i;
if ((i = Unread.posts.indexOf(post)) === -1) { ID = post.ID;
if (!Unread.posts[ID]) {
return; return;
} }
Unread.posts.splice(i, 1); if (post === Unread.posts.first) {
if (i === 0) { Unread.lastReadPost = ID;
Unread.lastReadPost = post.ID;
Unread.saveLastReadPost(); Unread.saveLastReadPost();
} }
Unread.posts.rm(ID);
if ((i = Unread.postsQuotingYou.indexOf(post)) !== -1) { if ((i = Unread.postsQuotingYou.indexOf(post)) !== -1) {
Unread.postsQuotingYou.splice(i, 1); Unread.postsQuotingYou.splice(i, 1);
} }
@ -11873,35 +11994,22 @@
} }
return arr.splice(0, i); return arr.splice(0, i);
}, },
read: $.debounce(50, function(e) { read: $.debounce(100, function(e) {
var ID, height, i, post, posts; var ID, height, post, posts;
if (d.hidden || !Unread.posts.length) { if (d.hidden || !Unread.posts.length) {
return; return;
} }
height = doc.clientHeight; height = doc.clientHeight;
posts = Unread.posts; posts = Unread.posts;
i = 0; while (post = posts.first) {
while (post = posts[i]) { if (!(Header.getBottomOf(post.nodes.root) > -1)) {
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; 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) { if (!ID) {
return; return;
@ -11930,7 +12038,7 @@
if (!(d.hidden || force === true)) { if (!(d.hidden || force === true)) {
return; return;
} }
if (!(post = Unread.posts[0])) { if (!(post = Unread.posts.first)) {
return $.rm(Unread.hr); return $.rm(Unread.hr);
} }
if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root)) { if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root)) {

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript // 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. * Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE * https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -88,7 +88,7 @@
'use strict'; 'use strict';
(function() { (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; }, __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, __slice = [].slice,
__hasProp = {}.hasOwnProperty, __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 = { Polyfill = {
init: function() { init: function() {
this.notificationPermission(); this.notificationPermission();
@ -7130,108 +7223,120 @@
innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>' innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>'
}); });
input = $('input', this.controls); input = $('input', this.controls);
$.on(input, 'change', QuoteThreading.toggle); $.on(input, 'change', this.toggle);
$.event('AddMenuEntry', { $.event('AddMenuEntry', {
type: 'header', type: 'header',
el: this.controls, el: this.controls,
order: 98 order: 98
}); });
if (!Conf['Unread Count']) {
$.on(d, '4chanXInitFinished', this.setup); $.on(d, '4chanXInitFinished', this.setup);
}
return Post.callbacks.push({ return Post.callbacks.push({
name: 'Quote Threading', name: 'Quote Threading',
cb: this.node cb: this.node
}); });
}, },
setup: function() { setup: function() {
var ID, post, posts; var ID, post, _ref;
$.off(d, '4chanXInitFinished', QuoteThreading.setup); $.off(d, '4chanXInitFinished', QuoteThreading.setup);
posts = g.posts; _ref = g.posts;
for (ID in posts) { for (ID in _ref) {
post = posts[ID]; post = _ref[ID];
if (post.cb) { if (post.cb) {
post.cb.call(post); post.cb();
} }
} }
return QuoteThreading.hasRun = true; return QuoteThreading.hasRun = true;
}, },
node: function() { node: function() {
var ID, fullID, keys, len, post, posts, qid, quote, quotes, uniq, _i, _len; var keys, len, post, posts, quote, _i, _len, _ref;
if (this.isClone || !QuoteThreading.enabled || this.thread.OP === this) {
return;
}
quotes = this.quotes, ID = this.ID, fullID = this.fullID;
posts = g.posts; posts = g.posts;
if (!(post = posts[fullID]) || post.isHidden) { if (this.isClone || !QuoteThreading.enabled) {
return; return;
} }
uniq = {}; if (Conf['Unread Count']) {
len = ("" + g.BOARD).length + 1; Unread.posts.push(this);
for (_i = 0, _len = quotes.length; _i < _len; _i++) {
quote = quotes[_i];
qid = quote;
if (!(qid.slice(len) < ID)) {
continue;
} }
if (qid in posts) { if (this.thread.OP === this || !(post = posts[this.fullID]) || post.isHidden) {
uniq[qid.slice(len)] = true; 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) { if (keys.length !== 1) {
return; return;
} }
this.threaded = "" + g.BOARD + "." + keys[0]; this.threaded = keys[0];
return this.cb = QuoteThreading.nodeinsert; return this.cb = QuoteThreading.nodeinsert;
}, },
nodeinsert: function() { nodeinsert: function() {
var bottom, height, qpost, qroot, threadContainer, top, _ref; var ID, bottom, height, post, posts, root, threadContainer, top, _ref;
qpost = g.posts[this.threaded]; post = g.posts[this.threaded];
delete this.threaded; posts = Unread.posts;
delete this.cb; if (this.thread.OP === post) {
if (this.thread.OP === qpost) {
return false; return false;
} }
if (QuoteThreading.hasRun) { if (QuoteThreading.hasRun) {
height = doc.clientHeight; height = doc.clientHeight;
_ref = qpost.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top; _ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
if (!(__indexOf.call(Unread.posts, qpost) >= 0 || ((bottom < height) && (top > 0)))) { if (!((posts != null ? posts[post.ID] : void 0) || ((bottom < height) && (top > 0)))) {
return false; return false;
} }
} }
qroot = qpost.nodes.root; root = post.nodes.root;
if (!$.hasClass(qroot, 'threadOP')) { if (!$.hasClass(root, 'threadOP')) {
$.addClass(qroot, 'threadOP'); $.addClass(root, 'threadOP');
threadContainer = $.el('div', { threadContainer = $.el('div', {
className: 'threadContainer' className: 'threadContainer'
}); });
$.after(qroot, threadContainer); $.after(root, threadContainer);
} else { } else {
threadContainer = qroot.nextSibling; threadContainer = root.nextSibling;
post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer));
} }
$.add(threadContainer, this.nodes.root); $.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; return true;
}, },
toggle: function() { 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'); thread = $('.thread');
replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread); replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
QuoteThreading.enabled = this.checked; if (QuoteThreading.enabled = this.checked) {
if (this.checked) {
QuoteThreading.hasRun = false; QuoteThreading.hasRun = false;
for (_i = 0, _len = replies.length; _i < _len; _i++) { for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i]; reply = replies[_i];
QuoteThreading.node.call(node = Get.postFromRoot(reply)); post = Get.postFromRoot(reply);
if (node.cb) { if (post.cb) {
node.cb(); post.cb();
} }
} }
QuoteThreading.hasRun = true; QuoteThreading.hasRun = true;
} else { } else {
replies.sort(function(a, b) { replies.sort(function(a, b) {
var aID, bID; return Number(a.id.slice(2)) - Number(b.id.slice(2));
aID = Number(a.id.slice(2));
bID = Number(b.id.slice(2));
return aID - bID;
}); });
$.add(thread, replies); $.add(thread, replies);
containers = $$('.threadContainer', thread); containers = $$('.threadContainer', thread);
@ -7245,12 +7350,15 @@
$.rmClass(post, 'threadOP'); $.rmClass(post, 'threadOP');
} }
} }
return Unread.update(true); if (Conf['Unread Count']) {
return Unread.read();
}
}, },
kb: function() { kb: function() {
var control; var control;
control = $.id('threadingControl'); control = $.id('threadingControl');
return control.click(); control.checked = !control.checked;
return QuoteThreading.toggle.call(control);
} }
}; };
@ -11152,7 +11260,7 @@
} }
root = post.nodes.root; root = post.nodes.root;
if (post.cb) { if (post.cb) {
if (!post.cb.call(post)) { if (!post.cb()) {
$.add(ThreadUpdater.root, root); $.add(ThreadUpdater.root, root);
} }
} else { } else {
@ -11682,8 +11790,19 @@
this.hr = $.el('hr', { this.hr = $.el('hr', {
id: 'unread-line' id: 'unread-line'
}); });
this.posts = []; this.posts = new RandomAccessList;
this.postsQuotingYou = []; 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({ return Thread.callbacks.push({
name: 'Unread', name: 'Unread',
cb: this.node cb: this.node
@ -11716,17 +11835,19 @@
} }
} }
Unread.addPosts(posts); Unread.addPosts(posts);
if (Conf['Quote Threading']) {
QuoteThreading.setup();
}
if (Conf['Scroll to Last Read Post']) {
return Unread.scroll(); return Unread.scroll();
}
}, },
scroll: function() { scroll: function() {
var down, hash, post, posts, root; 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) { if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return; return;
} }
if (post = Unread.posts[0]) { if (post = Unread.posts.first) {
while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root)) { while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root)) {
if (!(post = Get.postFromRoot(root)).isHidden) { if (!(post = Get.postFromRoot(root)).isHidden) {
break; break;
@ -11745,7 +11866,7 @@
} }
}, },
sync: function() { sync: function() {
var lastReadPost; var ID, lastReadPost, post;
lastReadPost = Unread.db.get({ lastReadPost = Unread.db.get({
boardID: Unread.thread.board.ID, boardID: Unread.thread.board.ID,
threadID: Unread.thread.ID, threadID: Unread.thread.ID,
@ -11755,7 +11876,14 @@
return; return;
} }
Unread.lastReadPost = lastReadPost; 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); Unread.readArray(Unread.postsQuotingYou);
if (Conf['Unread Line']) { if (Conf['Unread Line']) {
Unread.setLine(); Unread.setLine();
@ -11763,28 +11891,20 @@
return Unread.update(); return Unread.update();
}, },
addPosts: function(posts) { 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++) { for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i]; post = posts[_i];
ID = post.ID; ID = post.ID;
if (ID <= Unread.lastReadPost || post.isHidden) { if (ID <= Unread.lastReadPost || post.isHidden || Unread.qr(post)) {
continue; continue;
} }
if (QR.db) { if (!(post.prev || post.next)) {
data = {
boardID: post.board.ID,
threadID: post.thread.ID,
postID: post.ID
};
if (QR.db.get(data)) {
continue;
}
}
Unread.posts.push(post); Unread.posts.push(post);
}
Unread.addPostQuotingYou(post); Unread.addPostQuotingYou(post);
} }
if (Conf['Unread Line']) { 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(); Unread.read();
return Unread.update(); return Unread.update();
@ -11833,15 +11953,16 @@
} }
}, },
readSinglePost: function(post) { readSinglePost: function(post) {
var i; var ID, i;
if ((i = Unread.posts.indexOf(post)) === -1) { ID = post.ID;
if (!Unread.posts[ID]) {
return; return;
} }
Unread.posts.splice(i, 1); if (post === Unread.posts.first) {
if (i === 0) { Unread.lastReadPost = ID;
Unread.lastReadPost = post.ID;
Unread.saveLastReadPost(); Unread.saveLastReadPost();
} }
Unread.posts.rm(ID);
if ((i = Unread.postsQuotingYou.indexOf(post)) !== -1) { if ((i = Unread.postsQuotingYou.indexOf(post)) !== -1) {
Unread.postsQuotingYou.splice(i, 1); Unread.postsQuotingYou.splice(i, 1);
} }
@ -11857,35 +11978,22 @@
} }
return arr.splice(0, i); return arr.splice(0, i);
}, },
read: $.debounce(50, function(e) { read: $.debounce(100, function(e) {
var ID, height, i, post, posts; var ID, height, post, posts;
if (d.hidden || !Unread.posts.length) { if (d.hidden || !Unread.posts.length) {
return; return;
} }
height = doc.clientHeight; height = doc.clientHeight;
posts = Unread.posts; posts = Unread.posts;
i = 0; while (post = posts.first) {
while (post = posts[i]) { if (!(Header.getBottomOf(post.nodes.root) > -1)) {
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; 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) { if (!ID) {
return; return;
@ -11914,7 +12022,7 @@
if (!(d.hidden || force === true)) { if (!(d.hidden || force === true)) {
return; return;
} }
if (!(post = Unread.posts[0])) { if (!(post = Unread.posts.first)) {
return $.rm(Unread.hr); return $.rm(Unread.hr);
} }
if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root)) { if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root)) {

View File

@ -4,3 +4,4 @@
<%= grunt.file.read('src/General/lib/clone.class') %> <%= grunt.file.read('src/General/lib/clone.class') %>
<%= grunt.file.read('src/General/lib/databoard.class') %> <%= grunt.file.read('src/General/lib/databoard.class') %>
<%= grunt.file.read('src/General/lib/notice.class') %> <%= grunt.file.read('src/General/lib/notice.class') %>
<%= grunt.file.read('src/General/lib/randomaccesslist.class') %>

View File

@ -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)

View File

@ -296,7 +296,7 @@ ThreadUpdater =
continue unless posts.hasOwnProperty key continue unless posts.hasOwnProperty key
root = post.nodes.root root = post.nodes.root
if post.cb if post.cb
unless post.cb.call post unless post.cb()
$.add ThreadUpdater.root, root $.add ThreadUpdater.root, root
else else
$.add ThreadUpdater.root, root $.add ThreadUpdater.root, root

View File

@ -5,9 +5,19 @@ Unread =
@db = new DataBoard 'lastReadPosts', @sync @db = new DataBoard 'lastReadPosts', @sync
@hr = $.el 'hr', @hr = $.el 'hr',
id: 'unread-line' id: 'unread-line'
@posts = [] @posts = new RandomAccessList
@postsQuotingYou = [] @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 Thread.callbacks.push
name: 'Unread' name: 'Unread'
cb: @node cb: @node
@ -27,16 +37,15 @@ Unread =
ready: -> ready: ->
$.off d, '4chanXInitFinished', Unread.ready $.off d, '4chanXInitFinished', Unread.ready
posts = [] posts = []
for ID, post of Unread.thread.posts posts.push post for ID, post of Unread.thread.posts when post.isReply
posts.push post if post.isReply
Unread.addPosts posts Unread.addPosts posts
Unread.scroll() QuoteThreading.setup() if Conf['Quote Threading']
Unread.scroll() if Conf['Scroll to Last Read Post']
scroll: -> scroll: ->
return unless Conf['Scroll to Last Read Post']
# Let the header's onload callback handle it. # Let the header's onload callback handle it.
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts 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. # 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 while root = $.x 'preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root
break unless (post = Get.postFromRoot root).isHidden break unless (post = Get.postFromRoot root).isHidden
@ -57,7 +66,13 @@ Unread =
defaultValue: 0 defaultValue: 0
return unless Unread.lastReadPost < lastReadPost return unless Unread.lastReadPost < lastReadPost
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.readArray Unread.postsQuotingYou
Unread.setLine() if Conf['Unread Line'] Unread.setLine() if Conf['Unread Line']
Unread.update() Unread.update()
@ -65,19 +80,12 @@ Unread =
addPosts: (posts) -> addPosts: (posts) ->
for post in posts for post in posts
{ID} = post {ID} = post
if ID <= Unread.lastReadPost or post.isHidden continue if ID <= Unread.lastReadPost or post.isHidden or Unread.qr post
continue Unread.posts.push post unless post.prev or post.next
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
Unread.addPostQuotingYou post Unread.addPostQuotingYou post
if Conf['Unread Line'] if Conf['Unread Line']
# Force line on visible threads if there were no unread posts previously. # 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.read()
Unread.update() Unread.update()
@ -112,11 +120,12 @@ Unread =
Unread.addPosts e.detail.newPosts Unread.addPosts e.detail.newPosts
readSinglePost: (post) -> readSinglePost: (post) ->
return if (i = Unread.posts.indexOf post) is -1 {ID} = post
Unread.posts.splice i, 1 return unless Unread.posts[ID]
if i is 0 if post is Unread.posts.first
Unread.lastReadPost = post.ID Unread.lastReadPost = ID
Unread.saveLastReadPost() Unread.saveLastReadPost()
Unread.posts.rm ID
if (i = Unread.postsQuotingYou.indexOf post) isnt -1 if (i = Unread.postsQuotingYou.indexOf post) isnt -1
Unread.postsQuotingYou.splice i, 1 Unread.postsQuotingYou.splice i, 1
Unread.update() Unread.update()
@ -126,28 +135,18 @@ Unread =
break if post.ID > Unread.lastReadPost break if post.ID > Unread.lastReadPost
arr.splice 0, i arr.splice 0, i
read: $.debounce 50, (e) -> read: $.debounce 100, (e) ->
return if d.hidden or !Unread.posts.length return if d.hidden or !Unread.posts.length
height = doc.clientHeight height = doc.clientHeight
{posts} = Unread {posts} = Unread
i = 0 while post = posts.first
break unless Header.getBottomOf(post.nodes.root) > -1 # post is not completely read
while post = posts[i]
if Header.getBottomOf(post.nodes.root) > -1 # post is not completely read
{ID} = post {ID} = post
if Conf['Mark Quotes of You'] posts.rm ID
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'] if Conf['Mark Quotes of You'] and post.info.yours
posts.splice 0, i QuoteYou.lastRead = post.nodes.root
return unless ID return unless ID
@ -165,7 +164,7 @@ Unread =
setLine: (force) -> setLine: (force) ->
return unless d.hidden or force is true 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 if $.x 'preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root # not the first reply
$.before post.nodes.root, Unread.hr $.before post.nodes.root, Unread.hr

View File

@ -11,14 +11,14 @@ QuoteThreading =
innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>' innerHTML: '<label><input id=threadingControl type=checkbox checked> Threading</label>'
input = $ 'input', @controls input = $ 'input', @controls
$.on input, 'change', QuoteThreading.toggle $.on input, 'change', @toggle
$.event 'AddMenuEntry', $.event 'AddMenuEntry',
type: 'header' type: 'header'
el: @controls el: @controls
order: 98 order: 98
$.on d, '4chanXInitFinished', @setup $.on d, '4chanXInitFinished', @setup unless Conf['Unread Count']
Post.callbacks.push Post.callbacks.push
name: 'Quote Threading' name: 'Quote Threading'
@ -26,83 +26,91 @@ QuoteThreading =
setup: -> setup: ->
$.off d, '4chanXInitFinished', 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 QuoteThreading.hasRun = true
node: -> node: ->
return if @isClone or not QuoteThreading.enabled or @thread.OP is @
{quotes, ID, fullID} = @
{posts} = g {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 = {} return if @thread.OP is @ or !(post = posts[@fullID]) or post.isHidden # Filtered
len = "#{g.BOARD}".length + 1
for quote in quotes keys = []
qid = quote len = g.BOARD.ID.length + 1
continue unless qid[len..] < ID keys.push quote for quote in @quotes when (quote[len..] < @ID) and quote of posts
if qid of posts
uniq[qid[len..]] = true
keys = Object.keys uniq
return unless keys.length is 1 return unless keys.length is 1
@threaded = "#{g.BOARD}.#{keys[0]}" @threaded = keys[0]
@cb = QuoteThreading.nodeinsert @cb = QuoteThreading.nodeinsert
nodeinsert: -> nodeinsert: ->
qpost = g.posts[@threaded] post = g.posts[@threaded]
{posts} = Unread
delete @threaded return false if @thread.OP is post
delete @cb
return false if @thread.OP is qpost
if QuoteThreading.hasRun if QuoteThreading.hasRun
height = doc.clientHeight height = doc.clientHeight
{bottom, top} = qpost.nodes.root.getBoundingClientRect() {bottom, top} = post.nodes.root.getBoundingClientRect()
# Post is unread or is fully visible. # 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 {root} = post.nodes
unless $.hasClass qroot, 'threadOP' unless $.hasClass root, 'threadOP'
$.addClass qroot, 'threadOP' $.addClass root, 'threadOP'
threadContainer = $.el 'div', threadContainer = $.el 'div',
className: 'threadContainer' className: 'threadContainer'
$.after qroot, threadContainer $.after root, threadContainer
else else
threadContainer = qroot.nextSibling threadContainer = root.nextSibling
post = Get.postFromRoot $.x 'descendant::div[contains(@class,"postContainer")][last()]', threadContainer
$.add threadContainer, @nodes.root $.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 return true
toggle: -> toggle: ->
if Conf['Unread Count']
Unread.posts = new RandomAccessList
Unread.ready()
thread = $ '.thread' thread = $ '.thread'
replies = $$ '.thread > .replyContainer, .threadContainer > .replyContainer', thread replies = $$ '.thread > .replyContainer, .threadContainer > .replyContainer', thread
QuoteThreading.enabled = @checked
if @checked if QuoteThreading.enabled = @checked
QuoteThreading.hasRun = false QuoteThreading.hasRun = false
for reply in replies for reply in replies
QuoteThreading.node.call node = Get.postFromRoot reply post = Get.postFromRoot reply
node.cb() if node.cb # 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 QuoteThreading.hasRun = true
else else
replies.sort (a, b) -> replies.sort (a, b) -> Number(a.id[2..]) - Number(b.id[2..])
aID = Number a.id[2..]
bID = Number b.id[2..]
aID - bID
$.add thread, replies $.add thread, replies
containers = $$ '.threadContainer', thread containers = $$ '.threadContainer', thread
$.rm container for container in containers $.rm container for container in containers
$.rmClass post, 'threadOP' for post in $$ '.threadOP' $.rmClass post, 'threadOP' for post in $$ '.threadOP'
Unread.update true Unread.read() if Conf['Unread Count']
kb: -> kb: ->
control = $.id 'threadingControl' control = $.id 'threadingControl'
control.click() control.checked = not control.checked
QuoteThreading.toggle.call control