Unread.coffee

This commit is contained in:
Zixaphir 2015-01-09 22:52:52 -07:00
parent ac5a6c53b0
commit 5f771c9430
3 changed files with 460 additions and 403 deletions

View File

@ -13075,23 +13075,29 @@
Unread = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon'] && !Conf['Desktop Notifications']) {
if (!(g.VIEW === 'thread' && Conf['Unread Count'] || Conf['Unread Favicon'] || Conf['Unread Line'] || Conf['Scroll to Last Read Post'] || Conf['Thread Watcher'] || Conf['Desktop Notifications'] || Conf['Quote Threading'])) {
return;
}
this.db = new DataBoard('lastReadPosts', this.sync);
this.hr = $.el('hr', {
id: 'unread-line'
});
this.posts = new RandomAccessList;
this.postsQuotingYou = [];
return Thread.callbacks.push({
this.posts = new Set;
this.postsQuotingYou = new Set;
this.order = new RandomAccessList;
this.position = null;
Thread.callbacks.push({
name: 'Unread',
cb: this.node
});
return Post.callbacks.push({
name: 'Unread',
cb: this.addPost
});
},
disconnect: function() {
var hr, name, _i, _len, _ref;
if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon'] && !Conf['Desktop Notifications']) {
if (!(g.VIEW === 'thread' && Conf['Unread Count'] || Conf['Unread Favicon'] || Conf['Unread Line'] || Conf['Scroll to Last Read Post'] || Conf['Thread Watcher'] || Conf['Desktop Notifications'] || Conf['Quote Threading'])) {
return;
}
Unread.db.disconnect();
@ -13114,6 +13120,7 @@
return Thread.callbacks.disconnect('Unread');
},
node: function() {
var ID, _i, _len, _ref;
Unread.thread = this;
Unread.title = d.title;
Unread.lastReadPost = Unread.db.get({
@ -13121,118 +13128,108 @@
threadID: this.ID,
defaultValue: 0
});
$.on(d, '4chanXInitFinished', Unread.ready);
$.on(d, 'ThreadUpdate', Unread.onUpdate);
Unread.readCount = 0;
_ref = this.posts.keys;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
ID = _ref[_i];
if (+ID <= Unread.lastReadPost) {
Unread.readCount++;
}
}
$.one(d, '4chanXInitFinished', Unread.ready);
return $.on(d, 'ThreadUpdate', Unread.onUpdate);
},
ready: function() {
Unread.setLine(true);
Unread.read();
Unread.update();
if (Conf['Scroll to Last Read Post']) {
Unread.scroll();
}
$.on(d, 'scroll visibilitychange', Unread.read);
if (Conf['Unread Line']) {
return $.on(d, 'visibilitychange', Unread.setLine);
}
},
ready: function() {
var post, posts;
$.off(d, '4chanXInitFinished', Unread.ready);
posts = Unread.thread.posts;
post = posts.first().nodes.root;
return $.asap((function() {
return post.getBoundingClientRect().bottom;
}), function() {
var arr;
if (Conf['Quote Threading']) {
QuoteThreading.force();
} else {
arr = [];
posts.forEach(function(post) {
if (post.isReply) {
return arr.push(post);
}
});
Unread.addPosts(arr);
}
if (Conf['Scroll to Last Read Post']) {
return setTimeout(Unread.scroll, 200);
}
});
positionPrev: function() {
if (Unread.position) {
return Unread.position.prev;
} else {
return Unread.order.last;
}
},
scroll: function() {
var down, hash, keys, post, posts, root;
var hash, position, root;
if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return;
}
if (post = Unread.posts.first) {
while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.data.nodes.root)) {
if (!(post = Get.postFromRoot(root)).isHidden) {
break;
}
position = Unread.positionPrev();
while (position) {
root = position.data.nodes.root;
if (!root.getBoundingClientRect().height) {
position = position.prev;
} else {
Header.scrollToIfNeeded(root, true);
break;
}
if (!root) {
return;
}
down = true;
} else {
posts = Unread.thread.posts;
keys = posts.keys;
root = posts[keys[keys.length - 1]].nodes.root;
}
if (Header.getBottomOf(root) < 0) {
return Header.scrollTo(root, down);
}
},
sync: function() {
var ID, lastReadPost, post;
var ID, i, lastReadPost, postIDs, _i, _ref, _ref1;
if (Unread.lastReadPost == null) {
return;
}
lastReadPost = Unread.db.get({
boardID: Unread.thread.board.ID,
threadID: Unread.thread.ID,
defaultValue: 0
});
if (Unread.lastReadPost > lastReadPost) {
if (!(Unread.lastReadPost < lastReadPost)) {
return;
}
Unread.lastReadPost = lastReadPost;
post = Unread.posts.first;
while (post) {
ID = post.ID;
if (ID > lastReadPost) {
break;
postIDs = Unread.thread.posts.keys;
for (i = _i = _ref = Unread.readCount, _ref1 = postIDs.length; _i < _ref1; i = _i += 1) {
ID = +postIDs[i];
if (!Unread.thread.posts[ID].isFetchedQuote) {
if (ID > Unread.lastReadPost) {
break;
}
Unread.posts["delete"](ID);
Unread.postsQuotingYou["delete"](ID);
}
post = post.next;
Unread.posts.rm(ID);
}
Unread.readArray(Unread.postsQuotingYou);
if (Conf['Unread Line']) {
Unread.setLine();
Unread.readCount++;
}
Unread.updatePosition();
Unread.setLine();
return Unread.update();
},
addPosts: function(posts) {
var ID, post, _i, _len, _ref, _ref1;
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
ID = post.ID;
if (ID <= Unread.lastReadPost || post.isHidden || QR.db.get({
boardID: post.board.ID,
threadID: post.thread.ID,
postID: ID
})) {
continue;
}
Unread.posts.push(post);
Unread.addPostQuotingYou(post);
addPost: function() {
var _ref;
if (this.isFetchedQuote || this.isClone) {
return;
}
if (Conf['Unread Line']) {
Unread.setLine((_ref = (_ref1 = Unread.posts.first) != null ? _ref1.data : void 0, __indexOf.call(posts, _ref) >= 0));
Unread.order.push(this);
if (this.ID <= Unread.lastReadPost || this.isHidden || ((_ref = QR.db) != null ? _ref.get({
boardID: this.board.ID,
threadID: this.thread.ID,
postID: this.ID
}) : void 0)) {
return;
}
Unread.read();
return Unread.update();
Unread.posts.add(this.ID);
Unread.addPostQuotingYou(this);
return Unread.position != null ? Unread.position : Unread.position = Unread.order[this.ID];
},
addPostQuotingYou: function(post) {
var quotelink, _i, _len, _ref;
var quotelink, _i, _len, _ref, _ref1;
_ref = post.nodes.quotelinks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quotelink = _ref[_i];
if (!(QR.db.get(Get.postDataFromLink(quotelink)))) {
if (!((_ref1 = QR.db) != null ? _ref1.get(Get.postDataFromLink(quotelink)) : void 0)) {
continue;
}
Unread.postsQuotingYou.push(post);
Unread.postsQuotingYou.add(post.ID);
Unread.openNotification(post);
return;
}
@ -13242,8 +13239,8 @@
if (!Header.areNotificationsEnabled) {
return;
}
notif = new Notification("" + (post.getNameBlock()) + " replied to you", {
body: post.info.comment,
notif = new Notification("" + post.info.nameBlock + " replied to you", {
body: post.info[Conf['Remove Spoilers'] || Conf['Reveal Spoilers'] ? 'comment' : 'commentSpoilered'],
icon: Favicon.logo
});
notif.onclick = function() {
@ -13257,80 +13254,83 @@
};
},
onUpdate: function(e) {
if (e.detail[404]) {
return Unread.update();
} else if (Conf['Quote Threading']) {
if (!e.detail[404]) {
Unread.setLine();
Unread.read();
return Unread.update();
} else {
return Unread.addPosts([].map.call(e.detail.newPosts, function(fullID) {
return g.posts[fullID];
}));
}
},
readSinglePost: function(post) {
var ID, i, posts;
ID = post.ID;
posts = Unread.posts;
if (!posts[ID]) {
return;
}
if (post === posts.first) {
Unread.lastReadPost = ID;
Unread.saveLastReadPost();
}
posts.rm(ID);
if ((i = Unread.postsQuotingYou.indexOf(post)) !== -1) {
Unread.postsQuotingYou.splice(i, 1);
}
return Unread.update();
},
readArray: function(arr) {
var i, post, _i, _len;
for (i = _i = 0, _len = arr.length; _i < _len; i = ++_i) {
post = arr[i];
if (post.ID > Unread.lastReadPost) {
break;
}
}
return arr.splice(0, i);
},
read: $.debounce(100, function(e) {
var ID, data, post, posts;
if (d.hidden || !Unread.posts.length) {
readSinglePost: function(post) {
var ID;
ID = post.ID;
if (!Unread.posts.has(ID)) {
return;
}
posts = Unread.posts;
while (post = posts.first) {
if (!(Header.getBottomOf(post.data.nodes.root) > -1)) {
Unread.posts["delete"](ID);
Unread.postsQuotingYou["delete"](ID);
Unread.updatePosition();
Unread.saveLastReadPost();
return Unread.update();
},
read: $.debounce(100, function(e) {
var ID, count, data, height, root, _ref, _ref1;
if (d.hidden || !Unread.posts.size) {
return;
}
height = doc.clientHeight;
count = 0;
while (Unread.position) {
_ref = Unread.position, ID = _ref.ID, data = _ref.data;
root = data.nodes.root;
if (!(!root.getBoundingClientRect().height || Header.getBottomOf(root) > -1)) {
break;
}
ID = post.ID, data = post.data;
posts.rm(ID);
if (Conf['Mark Quotes of You'] && QR.db.get({
count++;
Unread.posts["delete"](ID);
Unread.postsQuotingYou["delete"](ID);
if (Conf['Mark Quotes of You'] && ((_ref1 = QR.db) != null ? _ref1.get({
boardID: data.board.ID,
threadID: data.thread.ID,
postID: ID
})) {
QuoteMarkers.lastRead = data.nodes.root;
}) : void 0)) {
QuoteYou.lastRead = root;
}
Unread.position = Unread.position.next;
}
if (!ID) {
if (!count) {
return;
}
if (Unread.lastReadPost < ID) {
Unread.lastReadPost = ID;
}
Unread.updatePosition();
Unread.saveLastReadPost();
Unread.readArray(Unread.postsQuotingYou);
if (e) {
return Unread.update();
}
}),
saveLastReadPost: $.debounce(5 * $.SECOND, function() {
if (Unread.thread.isDead) {
updatePosition: function() {
var _results;
_results = [];
while (Unread.position && !Unread.posts.has(Unread.position.ID)) {
_results.push(Unread.position = Unread.position.next);
}
return _results;
},
saveLastReadPost: $.debounce(2 * $.SECOND, function() {
var ID, i, postIDs, _i, _ref, _ref1;
postIDs = Unread.thread.posts.keys;
for (i = _i = _ref = Unread.readCount, _ref1 = postIDs.length; _i < _ref1; i = _i += 1) {
ID = +postIDs[i];
if (!Unread.thread.posts[ID].isFetchedQuote) {
if (Unread.posts.has(ID)) {
break;
}
Unread.lastReadPost = ID;
}
Unread.readCount++;
}
if (Unread.thread.isDead && !Unread.thread.isArchived) {
return;
}
Unread.db.forceSync();
return Unread.db.set({
boardID: Unread.thread.board.ID,
threadID: Unread.thread.ID,
@ -13338,27 +13338,39 @@
});
}),
setLine: function(force) {
var post;
if (!(d.hidden || force === true)) {
if (!Conf['Unread Line']) {
return;
}
if (!(post = Unread.posts.first)) {
return $.rm(Unread.hr);
}
if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.data.nodes.root)) {
return $.before(post.data.nodes.root, Unread.hr);
if (d.hidden || (force === true)) {
if (Unread.linePosition = Unread.positionPrev()) {
$.after(Unread.linePosition.data.nodes.root, Unread.hr);
} else {
$.rm(Unread.hr);
}
}
return Unread.hr.hidden = Unread.linePosition === Unread.order.last;
},
update: function() {
var count;
count = Unread.posts.length;
var count, countQuotingYou, titleCount, titleDead, titleQuotingYou;
count = Unread.posts.size;
countQuotingYou = Unread.postsQuotingYou.size;
if (Conf['Unread Count']) {
d.title = "" + (count || !Conf['Hide Unread Count at (0)'] ? "(" + count + ") " : '') + (g.DEAD ? Unread.title.replace('-', '- 404 -') : Unread.title);
titleQuotingYou = Conf['Quoted Title'] && countQuotingYou ? '(!) ' : '';
titleCount = count || !Conf['Hide Unread Count at (0)'] ? "(" + count + ") " : '';
titleDead = Unread.thread.isDead ? Unread.title.replace('-', (Unread.thread.isArchived ? '- Archived -' : '- 404 -')) : Unread.title;
d.title = "" + titleQuotingYou + titleCount + titleDead;
}
if (!(Unread.thread.isDead && !Unread.thread.isArchived)) {
ThreadWatcher.update(Unread.thread.board.ID, Unread.thread.ID, {
isDead: Unread.thread.isDead,
unread: count,
quotingYou: countQuotingYou
});
}
if (!Conf['Unread Favicon']) {
return;
}
Favicon.el.href = g.DEAD ? Unread.postsQuotingYou[0] ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? Unread.postsQuotingYou[0] ? Favicon.unreadY : Favicon.unread : Favicon["default"];
Favicon.el.href = Unread.thread.isDead ? countQuotingYou ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? countQuotingYou ? Favicon.unreadY : Favicon.unread : Favicon["default"];
return $.add(d.head, Favicon.el);
}
};

View File

@ -13064,23 +13064,29 @@
Unread = {
init: function() {
if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon'] && !Conf['Desktop Notifications']) {
if (!(g.VIEW === 'thread' && Conf['Unread Count'] || Conf['Unread Favicon'] || Conf['Unread Line'] || Conf['Scroll to Last Read Post'] || Conf['Thread Watcher'] || Conf['Desktop Notifications'] || Conf['Quote Threading'])) {
return;
}
this.db = new DataBoard('lastReadPosts', this.sync);
this.hr = $.el('hr', {
id: 'unread-line'
});
this.posts = new RandomAccessList;
this.postsQuotingYou = [];
return Thread.callbacks.push({
this.posts = new Set;
this.postsQuotingYou = new Set;
this.order = new RandomAccessList;
this.position = null;
Thread.callbacks.push({
name: 'Unread',
cb: this.node
});
return Post.callbacks.push({
name: 'Unread',
cb: this.addPost
});
},
disconnect: function() {
var hr, name, _i, _len, _ref;
if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon'] && !Conf['Desktop Notifications']) {
if (!(g.VIEW === 'thread' && Conf['Unread Count'] || Conf['Unread Favicon'] || Conf['Unread Line'] || Conf['Scroll to Last Read Post'] || Conf['Thread Watcher'] || Conf['Desktop Notifications'] || Conf['Quote Threading'])) {
return;
}
Unread.db.disconnect();
@ -13103,6 +13109,7 @@
return Thread.callbacks.disconnect('Unread');
},
node: function() {
var ID, _i, _len, _ref;
Unread.thread = this;
Unread.title = d.title;
Unread.lastReadPost = Unread.db.get({
@ -13110,118 +13117,108 @@
threadID: this.ID,
defaultValue: 0
});
$.on(d, '4chanXInitFinished', Unread.ready);
$.on(d, 'ThreadUpdate', Unread.onUpdate);
Unread.readCount = 0;
_ref = this.posts.keys;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
ID = _ref[_i];
if (+ID <= Unread.lastReadPost) {
Unread.readCount++;
}
}
$.one(d, '4chanXInitFinished', Unread.ready);
return $.on(d, 'ThreadUpdate', Unread.onUpdate);
},
ready: function() {
Unread.setLine(true);
Unread.read();
Unread.update();
if (Conf['Scroll to Last Read Post']) {
Unread.scroll();
}
$.on(d, 'scroll visibilitychange', Unread.read);
if (Conf['Unread Line']) {
return $.on(d, 'visibilitychange', Unread.setLine);
}
},
ready: function() {
var post, posts;
$.off(d, '4chanXInitFinished', Unread.ready);
posts = Unread.thread.posts;
post = posts.first().nodes.root;
return $.asap((function() {
return post.getBoundingClientRect().bottom;
}), function() {
var arr;
if (Conf['Quote Threading']) {
QuoteThreading.force();
} else {
arr = [];
posts.forEach(function(post) {
if (post.isReply) {
return arr.push(post);
}
});
Unread.addPosts(arr);
}
if (Conf['Scroll to Last Read Post']) {
return setTimeout(Unread.scroll, 200);
}
});
positionPrev: function() {
if (Unread.position) {
return Unread.position.prev;
} else {
return Unread.order.last;
}
},
scroll: function() {
var down, hash, keys, post, posts, root;
var hash, position, root;
if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return;
}
if (post = Unread.posts.first) {
while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.data.nodes.root)) {
if (!(post = Get.postFromRoot(root)).isHidden) {
break;
}
position = Unread.positionPrev();
while (position) {
root = position.data.nodes.root;
if (!root.getBoundingClientRect().height) {
position = position.prev;
} else {
Header.scrollToIfNeeded(root, true);
break;
}
if (!root) {
return;
}
down = true;
} else {
posts = Unread.thread.posts;
keys = posts.keys;
root = posts[keys[keys.length - 1]].nodes.root;
}
if (Header.getBottomOf(root) < 0) {
return Header.scrollTo(root, down);
}
},
sync: function() {
var ID, lastReadPost, post;
var ID, i, lastReadPost, postIDs, _i, _ref, _ref1;
if (Unread.lastReadPost == null) {
return;
}
lastReadPost = Unread.db.get({
boardID: Unread.thread.board.ID,
threadID: Unread.thread.ID,
defaultValue: 0
});
if (Unread.lastReadPost > lastReadPost) {
if (!(Unread.lastReadPost < lastReadPost)) {
return;
}
Unread.lastReadPost = lastReadPost;
post = Unread.posts.first;
while (post) {
ID = post.ID;
if (ID > lastReadPost) {
break;
postIDs = Unread.thread.posts.keys;
for (i = _i = _ref = Unread.readCount, _ref1 = postIDs.length; _i < _ref1; i = _i += 1) {
ID = +postIDs[i];
if (!Unread.thread.posts[ID].isFetchedQuote) {
if (ID > Unread.lastReadPost) {
break;
}
Unread.posts["delete"](ID);
Unread.postsQuotingYou["delete"](ID);
}
post = post.next;
Unread.posts.rm(ID);
}
Unread.readArray(Unread.postsQuotingYou);
if (Conf['Unread Line']) {
Unread.setLine();
Unread.readCount++;
}
Unread.updatePosition();
Unread.setLine();
return Unread.update();
},
addPosts: function(posts) {
var ID, post, _i, _len, _ref, _ref1;
for (_i = 0, _len = posts.length; _i < _len; _i++) {
post = posts[_i];
ID = post.ID;
if (ID <= Unread.lastReadPost || post.isHidden || QR.db.get({
boardID: post.board.ID,
threadID: post.thread.ID,
postID: ID
})) {
continue;
}
Unread.posts.push(post);
Unread.addPostQuotingYou(post);
addPost: function() {
var _ref;
if (this.isFetchedQuote || this.isClone) {
return;
}
if (Conf['Unread Line']) {
Unread.setLine((_ref = (_ref1 = Unread.posts.first) != null ? _ref1.data : void 0, __indexOf.call(posts, _ref) >= 0));
Unread.order.push(this);
if (this.ID <= Unread.lastReadPost || this.isHidden || ((_ref = QR.db) != null ? _ref.get({
boardID: this.board.ID,
threadID: this.thread.ID,
postID: this.ID
}) : void 0)) {
return;
}
Unread.read();
return Unread.update();
Unread.posts.add(this.ID);
Unread.addPostQuotingYou(this);
return Unread.position != null ? Unread.position : Unread.position = Unread.order[this.ID];
},
addPostQuotingYou: function(post) {
var quotelink, _i, _len, _ref;
var quotelink, _i, _len, _ref, _ref1;
_ref = post.nodes.quotelinks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quotelink = _ref[_i];
if (!(QR.db.get(Get.postDataFromLink(quotelink)))) {
if (!((_ref1 = QR.db) != null ? _ref1.get(Get.postDataFromLink(quotelink)) : void 0)) {
continue;
}
Unread.postsQuotingYou.push(post);
Unread.postsQuotingYou.add(post.ID);
Unread.openNotification(post);
return;
}
@ -13231,8 +13228,8 @@
if (!Header.areNotificationsEnabled) {
return;
}
notif = new Notification("" + (post.getNameBlock()) + " replied to you", {
body: post.info.comment,
notif = new Notification("" + post.info.nameBlock + " replied to you", {
body: post.info[Conf['Remove Spoilers'] || Conf['Reveal Spoilers'] ? 'comment' : 'commentSpoilered'],
icon: Favicon.logo
});
notif.onclick = function() {
@ -13246,80 +13243,83 @@
};
},
onUpdate: function(e) {
if (e.detail[404]) {
return Unread.update();
} else if (Conf['Quote Threading']) {
if (!e.detail[404]) {
Unread.setLine();
Unread.read();
return Unread.update();
} else {
return Unread.addPosts([].map.call(e.detail.newPosts, function(fullID) {
return g.posts[fullID];
}));
}
},
readSinglePost: function(post) {
var ID, i, posts;
ID = post.ID;
posts = Unread.posts;
if (!posts[ID]) {
return;
}
if (post === posts.first) {
Unread.lastReadPost = ID;
Unread.saveLastReadPost();
}
posts.rm(ID);
if ((i = Unread.postsQuotingYou.indexOf(post)) !== -1) {
Unread.postsQuotingYou.splice(i, 1);
}
return Unread.update();
},
readArray: function(arr) {
var i, post, _i, _len;
for (i = _i = 0, _len = arr.length; _i < _len; i = ++_i) {
post = arr[i];
if (post.ID > Unread.lastReadPost) {
break;
}
}
return arr.splice(0, i);
},
read: $.debounce(100, function(e) {
var ID, data, post, posts;
if (d.hidden || !Unread.posts.length) {
readSinglePost: function(post) {
var ID;
ID = post.ID;
if (!Unread.posts.has(ID)) {
return;
}
posts = Unread.posts;
while (post = posts.first) {
if (!(Header.getBottomOf(post.data.nodes.root) > -1)) {
Unread.posts["delete"](ID);
Unread.postsQuotingYou["delete"](ID);
Unread.updatePosition();
Unread.saveLastReadPost();
return Unread.update();
},
read: $.debounce(100, function(e) {
var ID, count, data, height, root, _ref, _ref1;
if (d.hidden || !Unread.posts.size) {
return;
}
height = doc.clientHeight;
count = 0;
while (Unread.position) {
_ref = Unread.position, ID = _ref.ID, data = _ref.data;
root = data.nodes.root;
if (!(!root.getBoundingClientRect().height || Header.getBottomOf(root) > -1)) {
break;
}
ID = post.ID, data = post.data;
posts.rm(ID);
if (Conf['Mark Quotes of You'] && QR.db.get({
count++;
Unread.posts["delete"](ID);
Unread.postsQuotingYou["delete"](ID);
if (Conf['Mark Quotes of You'] && ((_ref1 = QR.db) != null ? _ref1.get({
boardID: data.board.ID,
threadID: data.thread.ID,
postID: ID
})) {
QuoteMarkers.lastRead = data.nodes.root;
}) : void 0)) {
QuoteYou.lastRead = root;
}
Unread.position = Unread.position.next;
}
if (!ID) {
if (!count) {
return;
}
if (Unread.lastReadPost < ID) {
Unread.lastReadPost = ID;
}
Unread.updatePosition();
Unread.saveLastReadPost();
Unread.readArray(Unread.postsQuotingYou);
if (e) {
return Unread.update();
}
}),
saveLastReadPost: $.debounce(5 * $.SECOND, function() {
if (Unread.thread.isDead) {
updatePosition: function() {
var _results;
_results = [];
while (Unread.position && !Unread.posts.has(Unread.position.ID)) {
_results.push(Unread.position = Unread.position.next);
}
return _results;
},
saveLastReadPost: $.debounce(2 * $.SECOND, function() {
var ID, i, postIDs, _i, _ref, _ref1;
postIDs = Unread.thread.posts.keys;
for (i = _i = _ref = Unread.readCount, _ref1 = postIDs.length; _i < _ref1; i = _i += 1) {
ID = +postIDs[i];
if (!Unread.thread.posts[ID].isFetchedQuote) {
if (Unread.posts.has(ID)) {
break;
}
Unread.lastReadPost = ID;
}
Unread.readCount++;
}
if (Unread.thread.isDead && !Unread.thread.isArchived) {
return;
}
Unread.db.forceSync();
return Unread.db.set({
boardID: Unread.thread.board.ID,
threadID: Unread.thread.ID,
@ -13327,27 +13327,39 @@
});
}),
setLine: function(force) {
var post;
if (!(d.hidden || force === true)) {
if (!Conf['Unread Line']) {
return;
}
if (!(post = Unread.posts.first)) {
return $.rm(Unread.hr);
}
if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.data.nodes.root)) {
return $.before(post.data.nodes.root, Unread.hr);
if (d.hidden || (force === true)) {
if (Unread.linePosition = Unread.positionPrev()) {
$.after(Unread.linePosition.data.nodes.root, Unread.hr);
} else {
$.rm(Unread.hr);
}
}
return Unread.hr.hidden = Unread.linePosition === Unread.order.last;
},
update: function() {
var count;
count = Unread.posts.length;
var count, countQuotingYou, titleCount, titleDead, titleQuotingYou;
count = Unread.posts.size;
countQuotingYou = Unread.postsQuotingYou.size;
if (Conf['Unread Count']) {
d.title = "" + (count || !Conf['Hide Unread Count at (0)'] ? "(" + count + ") " : '') + (g.DEAD ? Unread.title.replace('-', '- 404 -') : Unread.title);
titleQuotingYou = Conf['Quoted Title'] && countQuotingYou ? '(!) ' : '';
titleCount = count || !Conf['Hide Unread Count at (0)'] ? "(" + count + ") " : '';
titleDead = Unread.thread.isDead ? Unread.title.replace('-', (Unread.thread.isArchived ? '- Archived -' : '- 404 -')) : Unread.title;
d.title = "" + titleQuotingYou + titleCount + titleDead;
}
if (!(Unread.thread.isDead && !Unread.thread.isArchived)) {
ThreadWatcher.update(Unread.thread.board.ID, Unread.thread.ID, {
isDead: Unread.thread.isDead,
unread: count,
quotingYou: countQuotingYou
});
}
if (!Conf['Unread Favicon']) {
return;
}
return Favicon.el.href = g.DEAD ? Unread.postsQuotingYou[0] ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? Unread.postsQuotingYou[0] ? Favicon.unreadY : Favicon.unread : Favicon["default"];
return Favicon.el.href = Unread.thread.isDead ? countQuotingYou ? Favicon.unreadDeadY : count ? Favicon.unreadDead : Favicon.dead : count ? countQuotingYou ? Favicon.unreadY : Favicon.unread : Favicon["default"];
}
};

View File

@ -1,19 +1,39 @@
Unread =
init: ->
return if g.VIEW isnt 'thread' or !Conf['Unread Count'] and !Conf['Unread Favicon'] and !Conf['Desktop Notifications']
return unless g.VIEW is 'thread' and
Conf['Unread Count'] or
Conf['Unread Favicon'] or
Conf['Unread Line'] or
Conf['Scroll to Last Read Post'] or
Conf['Thread Watcher'] or
Conf['Desktop Notifications'] or
Conf['Quote Threading']
@db = new DataBoard 'lastReadPosts', @sync
@hr = $.el 'hr',
id: 'unread-line'
@posts = new RandomAccessList
@postsQuotingYou = []
@posts = new Set
@postsQuotingYou = new Set
@order = new RandomAccessList
@position = null
Thread.callbacks.push
name: 'Unread'
cb: @node
Post.callbacks.push
name: 'Unread'
cb: @addPost
disconnect: ->
return if g.VIEW isnt 'thread' or !Conf['Unread Count'] and !Conf['Unread Favicon'] and !Conf['Desktop Notifications']
return unless g.VIEW is 'thread' and
Conf['Unread Count'] or
Conf['Unread Favicon'] or
Conf['Unread Line'] or
Conf['Scroll to Last Read Post'] or
Conf['Thread Watcher'] or
Conf['Desktop Notifications'] or
Conf['Quote Threading']
Unread.db.disconnect()
{hr} = Unread
@ -33,92 +53,84 @@ Unread =
Unread.thread = @
Unread.title = d.title
Unread.lastReadPost = Unread.db.get
boardID: @board.ID
threadID: @ID
boardID: @board.ID
threadID: @ID
defaultValue: 0
$.on d, '4chanXInitFinished', Unread.ready
$.on d, 'ThreadUpdate', Unread.onUpdate
$.on d, 'scroll visibilitychange', Unread.read
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
Unread.readCount = 0
Unread.readCount++ for ID in @posts.keys when +ID <= Unread.lastReadPost
$.one d, '4chanXInitFinished', Unread.ready
$.on d, 'ThreadUpdate', Unread.onUpdate
ready: ->
$.off d, '4chanXInitFinished', Unread.ready
{posts} = Unread.thread
post = posts.first().nodes.root
# XXX I'm guessing the browser isn't reflowing fast enough?
$.asap (-> post.getBoundingClientRect().bottom), ->
if Conf['Quote Threading']
QuoteThreading.force()
else
arr = []
posts.forEach (post) -> arr.push post if post.isReply
Unread.addPosts arr
setTimeout Unread.scroll, 200 if Conf['Scroll to Last Read Post']
Unread.setLine true
Unread.read()
Unread.update()
Unread.scroll() if Conf['Scroll to Last Read Post']
$.on d, 'scroll visibilitychange', Unread.read
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
positionPrev: ->
if Unread.position then Unread.position.prev else Unread.order.last
scroll: ->
# 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.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.data.nodes.root
break unless (post = Get.postFromRoot root).isHidden
return unless root
down = true
else
# Scroll to the last read post.
{posts} = Unread.thread
{keys} = posts
{root} = posts[keys[keys.length - 1]].nodes
# Scroll to the target unless we scrolled past it.
Header.scrollTo root, down if Header.getBottomOf(root) < 0
position = Unread.positionPrev()
while position
{root} = position.data.nodes
if !root.getBoundingClientRect().height
# Don't try to scroll to posts with display: none
position = position.prev
else
Header.scrollToIfNeeded root, true
break
return
sync: ->
return unless Unread.lastReadPost?
lastReadPost = Unread.db.get
boardID: Unread.thread.board.ID
threadID: Unread.thread.ID
boardID: Unread.thread.board.ID
threadID: Unread.thread.ID
defaultValue: 0
return if Unread.lastReadPost > lastReadPost
return unless Unread.lastReadPost < lastReadPost
Unread.lastReadPost = lastReadPost
post = Unread.posts.first
while post
{ID} = post
break if ID > lastReadPost
post = post.next
Unread.posts.rm ID
Unread.readArray Unread.postsQuotingYou
Unread.setLine() if Conf['Unread Line']
postIDs = Unread.thread.posts.keys
for i in [Unread.readCount...postIDs.length] by 1
ID = +postIDs[i]
unless Unread.thread.posts[ID].isFetchedQuote
break if ID > Unread.lastReadPost
Unread.posts.delete ID
Unread.postsQuotingYou.delete ID
Unread.readCount++
Unread.updatePosition()
Unread.setLine()
Unread.update()
addPosts: (posts) ->
for post in posts
{ID} = post
continue if ID <= Unread.lastReadPost or post.isHidden or QR.db.get {
boardID: post.board.ID
threadID: post.thread.ID
postID: ID
}
Unread.posts.push post
Unread.addPostQuotingYou post
if Conf['Unread Line']
# Force line on visible threads if there were no unread posts previously.
Unread.setLine Unread.posts.first?.data in posts
Unread.read()
Unread.update()
addPost: ->
return if @isFetchedQuote or @isClone
Unread.order.push @
return if @ID <= Unread.lastReadPost or @isHidden or QR.db?.get {
boardID: @board.ID
threadID: @thread.ID
postID: @ID
}
Unread.posts.add @ID
Unread.addPostQuotingYou @
Unread.position ?= Unread.order[@ID]
addPostQuotingYou: (post) ->
for quotelink in post.nodes.quotelinks when QR.db.get Get.postDataFromLink quotelink
Unread.postsQuotingYou.push post
for quotelink in post.nodes.quotelinks when QR.db?.get Get.postDataFromLink quotelink
Unread.postsQuotingYou.add post.ID
Unread.openNotification post
return
openNotification: (post) ->
return unless Header.areNotificationsEnabled
notif = new Notification "#{post.getNameBlock()} replied to you",
body: post.info.comment
notif = new Notification "#{post.info.nameBlock} replied to you",
body: post.info[if Conf['Remove Spoilers'] or Conf['Reveal Spoilers'] then 'comment' else 'commentSpoilered']
icon: Favicon.logo
notif.onclick = ->
Header.scrollToIfNeeded post.nodes.root, true
@ -129,78 +141,99 @@ Unread =
, 7 * $.SECOND
onUpdate: (e) ->
if e.detail[404]
Unread.update()
else if Conf['Quote Threading']
if !e.detail[404]
Unread.setLine()
Unread.read()
Unread.update()
else
Unread.addPosts [].map.call e.detail.newPosts, (fullID) -> g.posts[fullID]
Unread.update()
readSinglePost: (post) ->
{ID} = post
{posts} = Unread
return unless posts[ID]
if post is posts.first
Unread.lastReadPost = ID
Unread.saveLastReadPost()
posts.rm ID
if (i = Unread.postsQuotingYou.indexOf post) isnt -1
Unread.postsQuotingYou.splice i, 1
return unless Unread.posts.has ID
Unread.posts.delete ID
Unread.postsQuotingYou.delete ID
Unread.updatePosition()
Unread.saveLastReadPost()
Unread.update()
readArray: (arr) ->
for post, i in arr
break if post.ID > Unread.lastReadPost
arr.splice 0, i
read: $.debounce 100, (e) ->
return if d.hidden or !Unread.posts.length
{posts} = Unread
return if d.hidden or !Unread.posts.size
height = doc.clientHeight
while post = posts.first
break unless Header.getBottomOf(post.data.nodes.root) > -1 # post is not completely read
{ID, data} = post
posts.rm ID
count = 0
while Unread.position
{ID, data} = Unread.position
{root} = data.nodes
break unless !root.getBoundingClientRect().height or # post has been hidden
Header.getBottomOf(root) > -1 # post is completely read
count++
Unread.posts.delete ID
Unread.postsQuotingYou.delete ID
if Conf['Mark Quotes of You'] and QR.db.get {
if Conf['Mark Quotes of You'] and QR.db?.get {
boardID: data.board.ID
threadID: data.thread.ID
postID: ID
}
QuoteMarkers.lastRead = data.nodes.root
QuoteYou.lastRead = root
Unread.position = Unread.position.next
return unless ID
Unread.lastReadPost = ID if Unread.lastReadPost < ID
return unless count
Unread.updatePosition()
Unread.saveLastReadPost()
Unread.readArray Unread.postsQuotingYou
Unread.update() if e
saveLastReadPost: $.debounce 5 * $.SECOND, ->
return if Unread.thread.isDead
updatePosition: ->
while Unread.position and !Unread.posts.has Unread.position.ID
Unread.position = Unread.position.next
saveLastReadPost: $.debounce 2 * $.SECOND, ->
postIDs = Unread.thread.posts.keys
for i in [Unread.readCount...postIDs.length] by 1
ID = +postIDs[i]
unless Unread.thread.posts[ID].isFetchedQuote
break if Unread.posts.has ID
Unread.lastReadPost = ID
Unread.readCount++
return if Unread.thread.isDead and !Unread.thread.isArchived
Unread.db.forceSync()
Unread.db.set
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.first
if $.x 'preceding-sibling::div[contains(@class,"replyContainer")]', post.data.nodes.root # not the first reply
$.before post.data.nodes.root, Unread.hr
return unless Conf['Unread Line']
if d.hidden or (force is true)
if Unread.linePosition = Unread.positionPrev()
$.after Unread.linePosition.data.nodes.root, Unread.hr
else
$.rm Unread.hr
Unread.hr.hidden = Unread.linePosition is Unread.order.last
update: ->
count = Unread.posts.length
count = Unread.posts.size
countQuotingYou = Unread.postsQuotingYou.size
if Conf['Unread Count']
d.title = "#{if count or !Conf['Hide Unread Count at (0)'] then "(#{count}) " else ''}#{if g.DEAD then Unread.title.replace '-', '- 404 -' else Unread.title}"
titleQuotingYou = if Conf['Quoted Title'] and countQuotingYou then '(!) ' else ''
titleCount = if count or !Conf['Hide Unread Count at (0)'] then "(#{count}) " else ''
titleDead = if Unread.thread.isDead
Unread.title.replace '-', (if Unread.thread.isArchived then '- Archived -' else '- 404 -')
else
Unread.title
d.title = "#{titleQuotingYou}#{titleCount}#{titleDead}"
unless Unread.thread.isDead and !Unread.thread.isArchived
ThreadWatcher.update Unread.thread.board.ID, Unread.thread.ID,
isDead: Unread.thread.isDead
unread: count
quotingYou: countQuotingYou
return unless Conf['Unread Favicon']
Favicon.el.href =
if g.DEAD
if Unread.postsQuotingYou[0]
if Unread.thread.isDead
if countQuotingYou
Favicon.unreadDeadY
else if count
Favicon.unreadDead
@ -208,7 +241,7 @@ Unread =
Favicon.dead
else
if count
if Unread.postsQuotingYou[0]
if countQuotingYou
Favicon.unreadY
else
Favicon.unread