Optimizations to QuoteThreading (again)

This commit is contained in:
Zixaphir 2014-01-07 10:17:46 -07:00
parent a720406bad
commit 80dd30def0
6 changed files with 216 additions and 255 deletions

View File

@ -1534,19 +1534,6 @@
} }
}; };
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; return RandomAccessList;
})(); })();
@ -4951,16 +4938,18 @@
}); });
}, },
setup: function() { setup: function() {
var ID, post, _ref;
$.off(d, '4chanXInitFinished', QuoteThreading.setup); $.off(d, '4chanXInitFinished', QuoteThreading.setup);
return QuoteThreading.force();
},
force: function() {
var ID, post, _ref;
_ref = g.posts; _ref = g.posts;
for (ID in _ref) { for (ID in _ref) {
post = _ref[ID]; post = _ref[ID];
if (post.cb) { if (post.cb) {
post.cb(); post.cb(true);
} }
} }
return QuoteThreading.hasRun = true;
}, },
node: function() { node: function() {
var keys, len, post, posts, quote, _i, _len, _ref; var keys, len, post, posts, quote, _i, _len, _ref;
@ -4989,82 +4978,85 @@
this.threaded = keys[0]; this.threaded = keys[0];
return this.cb = QuoteThreading.nodeinsert; return this.cb = QuoteThreading.nodeinsert;
}, },
nodeinsert: function() { nodeinsert: function(force) {
var ID, bottom, height, post, posts, root, threadContainer, top, _ref; var bottom, height, post, posts, root, threadContainer, top, _ref;
post = g.posts[this.threaded]; post = g.posts[this.threaded];
posts = Unread.posts; posts = Unread.posts;
root = post.nodes.root;
if (this.thread.OP === post) { if (this.thread.OP === post) {
return false; return false;
} }
if (QuoteThreading.hasRun) { if (!force) {
height = doc.clientHeight; height = doc.clientHeight;
_ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top; _ref = root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
if (!((posts != null ? posts[post.ID] : void 0) || ((bottom < height) && (top > 0)))) { if (!((Conf['Unread Count'] && posts[post.ID]) || ((bottom < height) && (top > 0)))) {
return false; return false;
} }
} }
root = post.nodes.root; if ($.hasClass(root, 'threadOP')) {
if (!$.hasClass(root, 'threadOP')) { threadContainer = root.nextElementSibling;
$.addClass(root, 'threadOP'); post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer));
$.add(threadContainer, this.nodes.root);
} else {
threadContainer = $.el('div', { threadContainer = $.el('div', {
className: 'threadContainer' className: 'threadContainer'
}); });
$.add(threadContainer, this.nodes.root);
$.after(root, threadContainer); $.after(root, threadContainer);
} else { $.addClass(root, 'threadOP');
threadContainer = root.nextSibling;
post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer));
} }
$.add(threadContainer, this.nodes.root);
if (!Conf['Unread Count']) { if (!Conf['Unread Count']) {
return true; return true;
} }
if (posts[post.ID]) { if (posts[post.ID]) {
posts.after(post, this); posts.after(post, this);
return true;
}
if ((ID = posts.closest(post.ID)) !== -1) {
posts.after(posts[ID], this);
} else { } else {
posts.prepend(this); posts.prepend(this);
} }
return true; return true;
}, },
toggle: function() { toggle: function() {
var container, containers, post, replies, reply, thread, _i, _j, _k, _len, _len1, _len2, _ref; var ID, container, containers, post, posts, thread, _i, _j, _len, _len1, _ref, _results;
if (Conf['Unread Count']) {
Unread.posts = new RandomAccessList;
Unread.ready();
}
thread = $('.thread');
replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
if (QuoteThreading.enabled = this.checked) { if (QuoteThreading.enabled = this.checked) {
QuoteThreading.hasRun = false; return QuoteThreading.force();
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
post = Get.postFromRoot(reply);
if (post.cb) {
post.cb();
}
}
QuoteThreading.hasRun = true;
} else { } else {
replies.sort(function(a, b) { thread = $('.thread');
return Number(a.id.slice(2)) - Number(b.id.slice(2)); posts = (function() {
var _ref, _results;
_ref = g.posts;
_results = [];
for (ID in _ref) {
post = _ref[ID];
if (!(post === post.thread.OP || post.isClone)) {
_results.push(post);
}
}
return _results;
})();
posts.sort(function(a, b) {
return a.ID - b.ID;
}); });
$.add(thread, replies); $.add(thread, (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
_results.push(post.nodes.root);
}
return _results;
})());
containers = $$('.threadContainer', thread); containers = $$('.threadContainer', thread);
for (_j = 0, _len1 = containers.length; _j < _len1; _j++) { for (_i = 0, _len = containers.length; _i < _len; _i++) {
container = containers[_j]; container = containers[_i];
$.rm(container); $.rm(container);
} }
_ref = $$('.threadOP'); _ref = $$('.threadOP');
for (_k = 0, _len2 = _ref.length; _k < _len2; _k++) { _results = [];
post = _ref[_k]; for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
$.rmClass(post, 'threadOP'); post = _ref[_j];
_results.push($.rmClass(post, 'threadOP'));
} }
} return _results;
if (Conf['Unread Count']) {
return Unread.read();
} }
}, },
kb: function() { kb: function() {
@ -9547,7 +9539,7 @@
} }
Unread.addPosts(posts); Unread.addPosts(posts);
if (Conf['Quote Threading']) { if (Conf['Quote Threading']) {
QuoteThreading.setup(); QuoteThreading.force();
} }
if (Conf['Scroll to Last Read Post']) { if (Conf['Scroll to Last Read Post']) {
return Unread.scroll(); return Unread.scroll();
@ -12259,41 +12251,7 @@
Main = { Main = {
init: function() { init: function() {
var db, flatten, _i, _len, _ref; var db, flatten, pathname, _i, _len, _ref, _ref1;
flatten = function(parent, obj) {
var key, val;
if (obj instanceof Array) {
Conf[parent] = obj[0];
} else if (typeof obj === 'object') {
for (key in obj) {
val = obj[key];
flatten(key, val);
}
} else {
Conf[parent] = obj;
}
};
flatten(null, Config);
_ref = DataBoard.keys;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
db = _ref[_i];
Conf[db] = {
boards: {}
};
}
Conf['selectedArchives'] = {};
Conf['CachedTitles'] = [];
$.get(Conf, function(items) {
$.extend(Conf, items);
return Main.initFeatures();
});
$.on(d, '4chanMainInit', Main.initStyle);
return $.asap((function() {
return d.head && $('link[rel="shortcut icon"]', d.head) || d.readyState !== 'loading';
}), Main.initStyle);
},
initFeatures: function() {
var init, pathname, _ref;
pathname = location.pathname.split('/'); pathname = location.pathname.split('/');
g.BOARD = new Board(pathname[1]); g.BOARD = new Board(pathname[1]);
if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') { if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') {
@ -12312,6 +12270,40 @@
if (g.VIEW === 'thread') { if (g.VIEW === 'thread') {
g.THREADID = +pathname[3]; g.THREADID = +pathname[3];
} }
flatten = function(parent, obj) {
var key, val;
if (obj instanceof Array) {
Conf[parent] = obj[0];
} else if (typeof obj === 'object') {
for (key in obj) {
val = obj[key];
flatten(key, val);
}
} else {
Conf[parent] = obj;
}
};
flatten(null, Config);
_ref1 = DataBoard.keys;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
db = _ref1[_i];
Conf[db] = {
boards: {}
};
}
Conf['selectedArchives'] = {};
Conf['CachedTitles'] = [];
$.get(Conf, function(items) {
$.extend(Conf, items);
return Main.initFeatures();
});
$.on(d, '4chanMainInit', Main.initStyle);
return $.asap((function() {
return d.head && $('link[rel="shortcut icon"]', d.head) || d.readyState !== 'loading';
}), Main.initStyle);
},
initFeatures: function() {
var init;
switch (location.hostname) { switch (location.hostname) {
case 'a.4cdn.org': case 'a.4cdn.org':
return; return;
@ -12320,8 +12312,8 @@
return; return;
case 'i.4cdn.org': case 'i.4cdn.org':
$.ready(function() { $.ready(function() {
var URL, _ref1; var URL, pathname, _ref;
if (Conf['404 Redirect'] && ((_ref1 = d.title) === '4chan - Temporarily Offline' || _ref1 === '4chan - 404 Not Found')) { if (Conf['404 Redirect'] && ((_ref = d.title) === '4chan - Temporarily Offline' || _ref === '4chan - 404 Not Found')) {
Redirect.init(); Redirect.init();
pathname = location.pathname.split('/'); pathname = location.pathname.split('/');
URL = Redirect.to('file', { URL = Redirect.to('file', {

View File

@ -1540,19 +1540,6 @@
} }
}; };
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; return RandomAccessList;
})(); })();
@ -4954,16 +4941,18 @@
}); });
}, },
setup: function() { setup: function() {
var ID, post, _ref;
$.off(d, '4chanXInitFinished', QuoteThreading.setup); $.off(d, '4chanXInitFinished', QuoteThreading.setup);
return QuoteThreading.force();
},
force: function() {
var ID, post, _ref;
_ref = g.posts; _ref = g.posts;
for (ID in _ref) { for (ID in _ref) {
post = _ref[ID]; post = _ref[ID];
if (post.cb) { if (post.cb) {
post.cb(); post.cb(true);
} }
} }
return QuoteThreading.hasRun = true;
}, },
node: function() { node: function() {
var keys, len, post, posts, quote, _i, _len, _ref; var keys, len, post, posts, quote, _i, _len, _ref;
@ -4992,82 +4981,85 @@
this.threaded = keys[0]; this.threaded = keys[0];
return this.cb = QuoteThreading.nodeinsert; return this.cb = QuoteThreading.nodeinsert;
}, },
nodeinsert: function() { nodeinsert: function(force) {
var ID, bottom, height, post, posts, root, threadContainer, top, _ref; var bottom, height, post, posts, root, threadContainer, top, _ref;
post = g.posts[this.threaded]; post = g.posts[this.threaded];
posts = Unread.posts; posts = Unread.posts;
root = post.nodes.root;
if (this.thread.OP === post) { if (this.thread.OP === post) {
return false; return false;
} }
if (QuoteThreading.hasRun) { if (!force) {
height = doc.clientHeight; height = doc.clientHeight;
_ref = post.nodes.root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top; _ref = root.getBoundingClientRect(), bottom = _ref.bottom, top = _ref.top;
if (!((posts != null ? posts[post.ID] : void 0) || ((bottom < height) && (top > 0)))) { if (!((Conf['Unread Count'] && posts[post.ID]) || ((bottom < height) && (top > 0)))) {
return false; return false;
} }
} }
root = post.nodes.root; if ($.hasClass(root, 'threadOP')) {
if (!$.hasClass(root, 'threadOP')) { threadContainer = root.nextElementSibling;
$.addClass(root, 'threadOP'); post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer));
$.add(threadContainer, this.nodes.root);
} else {
threadContainer = $.el('div', { threadContainer = $.el('div', {
className: 'threadContainer' className: 'threadContainer'
}); });
$.add(threadContainer, this.nodes.root);
$.after(root, threadContainer); $.after(root, threadContainer);
} else { $.addClass(root, 'threadOP');
threadContainer = root.nextSibling;
post = Get.postFromRoot($.x('descendant::div[contains(@class,"postContainer")][last()]', threadContainer));
} }
$.add(threadContainer, this.nodes.root);
if (!Conf['Unread Count']) { if (!Conf['Unread Count']) {
return true; return true;
} }
if (posts[post.ID]) { if (posts[post.ID]) {
posts.after(post, this); posts.after(post, this);
return true;
}
if ((ID = posts.closest(post.ID)) !== -1) {
posts.after(posts[ID], this);
} else { } else {
posts.prepend(this); posts.prepend(this);
} }
return true; return true;
}, },
toggle: function() { toggle: function() {
var container, containers, post, replies, reply, thread, _i, _j, _k, _len, _len1, _len2, _ref; var ID, container, containers, post, posts, thread, _i, _j, _len, _len1, _ref, _results;
if (Conf['Unread Count']) {
Unread.posts = new RandomAccessList;
Unread.ready();
}
thread = $('.thread');
replies = $$('.thread > .replyContainer, .threadContainer > .replyContainer', thread);
if (QuoteThreading.enabled = this.checked) { if (QuoteThreading.enabled = this.checked) {
QuoteThreading.hasRun = false; return QuoteThreading.force();
for (_i = 0, _len = replies.length; _i < _len; _i++) {
reply = replies[_i];
post = Get.postFromRoot(reply);
if (post.cb) {
post.cb();
}
}
QuoteThreading.hasRun = true;
} else { } else {
replies.sort(function(a, b) { thread = $('.thread');
return Number(a.id.slice(2)) - Number(b.id.slice(2)); posts = (function() {
var _ref, _results;
_ref = g.posts;
_results = [];
for (ID in _ref) {
post = _ref[ID];
if (!(post === post.thread.OP || post.isClone)) {
_results.push(post);
}
}
return _results;
})();
posts.sort(function(a, b) {
return a.ID - b.ID;
}); });
$.add(thread, replies); $.add(thread, (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
_results.push(post.nodes.root);
}
return _results;
})());
containers = $$('.threadContainer', thread); containers = $$('.threadContainer', thread);
for (_j = 0, _len1 = containers.length; _j < _len1; _j++) { for (_i = 0, _len = containers.length; _i < _len; _i++) {
container = containers[_j]; container = containers[_i];
$.rm(container); $.rm(container);
} }
_ref = $$('.threadOP'); _ref = $$('.threadOP');
for (_k = 0, _len2 = _ref.length; _k < _len2; _k++) { _results = [];
post = _ref[_k]; for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
$.rmClass(post, 'threadOP'); post = _ref[_j];
_results.push($.rmClass(post, 'threadOP'));
} }
} return _results;
if (Conf['Unread Count']) {
return Unread.read();
} }
}, },
kb: function() { kb: function() {
@ -9530,7 +9522,7 @@
} }
Unread.addPosts(posts); Unread.addPosts(posts);
if (Conf['Quote Threading']) { if (Conf['Quote Threading']) {
QuoteThreading.setup(); QuoteThreading.force();
} }
if (Conf['Scroll to Last Read Post']) { if (Conf['Scroll to Last Read Post']) {
return Unread.scroll(); return Unread.scroll();
@ -12246,41 +12238,7 @@
Main = { Main = {
init: function() { init: function() {
var db, flatten, _i, _len, _ref; var db, flatten, pathname, _i, _len, _ref, _ref1;
flatten = function(parent, obj) {
var key, val;
if (obj instanceof Array) {
Conf[parent] = obj[0];
} else if (typeof obj === 'object') {
for (key in obj) {
val = obj[key];
flatten(key, val);
}
} else {
Conf[parent] = obj;
}
};
flatten(null, Config);
_ref = DataBoard.keys;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
db = _ref[_i];
Conf[db] = {
boards: {}
};
}
Conf['selectedArchives'] = {};
Conf['CachedTitles'] = [];
$.get(Conf, function(items) {
$.extend(Conf, items);
return Main.initFeatures();
});
$.on(d, '4chanMainInit', Main.initStyle);
return $.asap((function() {
return d.head && $('link[rel="shortcut icon"]', d.head) || d.readyState !== 'loading';
}), Main.initStyle);
},
initFeatures: function() {
var init, pathname, _ref;
pathname = location.pathname.split('/'); pathname = location.pathname.split('/');
g.BOARD = new Board(pathname[1]); g.BOARD = new Board(pathname[1]);
if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') { if ((_ref = g.BOARD.ID) === 'z' || _ref === 'fk') {
@ -12299,6 +12257,40 @@
if (g.VIEW === 'thread') { if (g.VIEW === 'thread') {
g.THREADID = +pathname[3]; g.THREADID = +pathname[3];
} }
flatten = function(parent, obj) {
var key, val;
if (obj instanceof Array) {
Conf[parent] = obj[0];
} else if (typeof obj === 'object') {
for (key in obj) {
val = obj[key];
flatten(key, val);
}
} else {
Conf[parent] = obj;
}
};
flatten(null, Config);
_ref1 = DataBoard.keys;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
db = _ref1[_i];
Conf[db] = {
boards: {}
};
}
Conf['selectedArchives'] = {};
Conf['CachedTitles'] = [];
$.get(Conf, function(items) {
$.extend(Conf, items);
return Main.initFeatures();
});
$.on(d, '4chanMainInit', Main.initStyle);
return $.asap((function() {
return d.head && $('link[rel="shortcut icon"]', d.head) || d.readyState !== 'loading';
}), Main.initStyle);
},
initFeatures: function() {
var init;
switch (location.hostname) { switch (location.hostname) {
case 'a.4cdn.org': case 'a.4cdn.org':
return; return;
@ -12307,8 +12299,8 @@
return; return;
case 'i.4cdn.org': case 'i.4cdn.org':
$.ready(function() { $.ready(function() {
var URL, _ref1; var URL, pathname, _ref;
if (Conf['404 Redirect'] && ((_ref1 = d.title) === '4chan - Temporarily Offline' || _ref1 === '4chan - 404 Not Found')) { if (Conf['404 Redirect'] && ((_ref = d.title) === '4chan - Temporarily Offline' || _ref === '4chan - 404 Not Found')) {
Redirect.init(); Redirect.init();
pathname = location.pathname.split('/'); pathname = location.pathname.split('/');
URL = Redirect.to('file', { URL = Redirect.to('file', {

View File

@ -1,5 +1,19 @@
Main = Main =
init: -> init: ->
pathname = location.pathname.split '/'
g.BOARD = new Board pathname[1]
return if g.BOARD.ID in ['z', 'fk']
g.VIEW =
switch pathname[2]
when 'res'
'thread'
when 'catalog'
'catalog'
else
'index'
if g.VIEW is 'thread'
g.THREADID = +pathname[3]
# flatten Config into Conf # flatten Config into Conf
# and get saved or default values # and get saved or default values
flatten = (parent, obj) -> flatten = (parent, obj) ->
@ -25,21 +39,6 @@ Main =
Main.initStyle Main.initStyle
initFeatures: -> initFeatures: ->
pathname = location.pathname.split '/'
g.BOARD = new Board pathname[1]
return if g.BOARD.ID in ['z', 'fk']
g.VIEW =
switch pathname[2]
when 'res'
'thread'
when 'catalog'
'catalog'
else
'index'
if g.VIEW is 'thread'
g.THREADID = +pathname[3]
switch location.hostname switch location.hostname
when 'a.4cdn.org' when 'a.4cdn.org'
return return

View File

@ -55,13 +55,4 @@ class RandomAccessList
if next if next
next.prev = prev next.prev = prev
else else
@last = prev @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

@ -39,7 +39,7 @@ Unread =
posts = [] posts = []
posts.push post for ID, post of Unread.thread.posts when post.isReply posts.push post for ID, post of Unread.thread.posts when post.isReply
Unread.addPosts posts Unread.addPosts posts
QuoteThreading.setup() if Conf['Quote Threading'] QuoteThreading.force() if Conf['Quote Threading']
Unread.scroll() if Conf['Scroll to Last Read Post'] Unread.scroll() if Conf['Scroll to Last Read Post']
scroll: -> scroll: ->

View File

@ -26,9 +26,11 @@ QuoteThreading =
setup: -> setup: ->
$.off d, '4chanXInitFinished', QuoteThreading.setup $.off d, '4chanXInitFinished', QuoteThreading.setup
QuoteThreading.force()
post.cb() for ID, post of g.posts when post.cb force: ->
QuoteThreading.hasRun = true post.cb true for ID, post of g.posts when post.cb
return
node: -> node: ->
{posts} = g {posts} = g
@ -46,69 +48,54 @@ QuoteThreading =
@threaded = keys[0] @threaded = keys[0]
@cb = QuoteThreading.nodeinsert @cb = QuoteThreading.nodeinsert
nodeinsert: -> nodeinsert: (force) ->
post = g.posts[@threaded] post = g.posts[@threaded]
{posts} = Unread {posts} = Unread
{root} = post.nodes
return false if @thread.OP is post return false if @thread.OP is post
if QuoteThreading.hasRun unless force
height = doc.clientHeight height = doc.clientHeight
{bottom, top} = post.nodes.root.getBoundingClientRect() {bottom, top} = root.getBoundingClientRect()
# Post is unread or is fully visible. # Post is unread or is fully visible.
return false unless posts?[post.ID] or ((bottom < height) and (top > 0)) return false unless (Conf['Unread Count'] and posts[post.ID]) or ((bottom < height) and (top > 0))
{root} = post.nodes if $.hasClass root, 'threadOP'
unless $.hasClass root, 'threadOP' threadContainer = root.nextElementSibling
$.addClass root, 'threadOP' post = Get.postFromRoot $.x 'descendant::div[contains(@class,"postContainer")][last()]', threadContainer
$.add threadContainer, @nodes.root
else
threadContainer = $.el 'div', threadContainer = $.el 'div',
className: 'threadContainer' className: 'threadContainer'
$.add threadContainer, @nodes.root
$.after root, threadContainer $.after root, threadContainer
else $.addClass root, 'threadOP'
threadContainer = root.nextSibling
post = Get.postFromRoot $.x 'descendant::div[contains(@class,"postContainer")][last()]', threadContainer
$.add threadContainer, @nodes.root
return true unless Conf['Unread Count'] return true unless Conf['Unread Count']
if posts[post.ID] if posts[post.ID]
posts.after post, @ posts.after post, @
return true
if (ID = posts.closest post.ID) isnt -1
posts.after posts[ID], @
else else
posts.prepend @ posts.prepend @
return true return true
toggle: -> toggle: ->
if Conf['Unread Count']
Unread.posts = new RandomAccessList
Unread.ready()
thread = $ '.thread'
replies = $$ '.thread > .replyContainer, .threadContainer > .replyContainer', thread
if QuoteThreading.enabled = @checked if QuoteThreading.enabled = @checked
QuoteThreading.hasRun = false QuoteThreading.force()
for reply in replies
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 else
replies.sort (a, b) -> Number(a.id[2..]) - Number(b.id[2..]) thread = $('.thread')
$.add thread, replies posts = (post for ID, post of g.posts when not (post is post.thread.OP or post.isClone))
posts.sort (a, b) -> a.ID - b.ID
$.add thread, (post.nodes.root for post in posts)
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.read() if Conf['Unread Count']
kb: -> kb: ->
control = $.id 'threadingControl' control = $.id 'threadingControl'