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 = { Unread = {
init: function() { 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; return;
} }
this.db = new DataBoard('lastReadPosts', this.sync); this.db = new DataBoard('lastReadPosts', this.sync);
this.hr = $.el('hr', { this.hr = $.el('hr', {
id: 'unread-line' id: 'unread-line'
}); });
this.posts = new RandomAccessList; this.posts = new Set;
this.postsQuotingYou = []; this.postsQuotingYou = new Set;
return Thread.callbacks.push({ this.order = new RandomAccessList;
this.position = null;
Thread.callbacks.push({
name: 'Unread', name: 'Unread',
cb: this.node cb: this.node
}); });
return Post.callbacks.push({
name: 'Unread',
cb: this.addPost
});
}, },
disconnect: function() { disconnect: function() {
var hr, name, _i, _len, _ref; 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; return;
} }
Unread.db.disconnect(); Unread.db.disconnect();
@ -13114,6 +13120,7 @@
return Thread.callbacks.disconnect('Unread'); return Thread.callbacks.disconnect('Unread');
}, },
node: function() { node: function() {
var ID, _i, _len, _ref;
Unread.thread = this; Unread.thread = this;
Unread.title = d.title; Unread.title = d.title;
Unread.lastReadPost = Unread.db.get({ Unread.lastReadPost = Unread.db.get({
@ -13121,118 +13128,108 @@
threadID: this.ID, threadID: this.ID,
defaultValue: 0 defaultValue: 0
}); });
$.on(d, '4chanXInitFinished', Unread.ready); Unread.readCount = 0;
$.on(d, 'ThreadUpdate', Unread.onUpdate); _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); $.on(d, 'scroll visibilitychange', Unread.read);
if (Conf['Unread Line']) { if (Conf['Unread Line']) {
return $.on(d, 'visibilitychange', Unread.setLine); return $.on(d, 'visibilitychange', Unread.setLine);
} }
}, },
ready: function() { positionPrev: function() {
var post, posts; if (Unread.position) {
$.off(d, '4chanXInitFinished', Unread.ready); return Unread.position.prev;
posts = Unread.thread.posts; } else {
post = posts.first().nodes.root; return Unread.order.last;
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);
}
});
}, },
scroll: function() { 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) { if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) {
return; return;
} }
if (post = Unread.posts.first) { position = Unread.positionPrev();
while (root = $.x('preceding-sibling::div[contains(@class,"replyContainer")][1]', post.data.nodes.root)) { while (position) {
if (!(post = Get.postFromRoot(root)).isHidden) { root = position.data.nodes.root;
break; 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() { sync: function() {
var ID, lastReadPost, post; var ID, i, lastReadPost, postIDs, _i, _ref, _ref1;
if (Unread.lastReadPost == null) {
return;
}
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,
defaultValue: 0 defaultValue: 0
}); });
if (Unread.lastReadPost > lastReadPost) { if (!(Unread.lastReadPost < lastReadPost)) {
return; return;
} }
Unread.lastReadPost = lastReadPost; Unread.lastReadPost = lastReadPost;
post = Unread.posts.first; postIDs = Unread.thread.posts.keys;
while (post) { for (i = _i = _ref = Unread.readCount, _ref1 = postIDs.length; _i < _ref1; i = _i += 1) {
ID = post.ID; ID = +postIDs[i];
if (ID > lastReadPost) { if (!Unread.thread.posts[ID].isFetchedQuote) {
break; if (ID > Unread.lastReadPost) {
break;
}
Unread.posts["delete"](ID);
Unread.postsQuotingYou["delete"](ID);
} }
post = post.next; Unread.readCount++;
Unread.posts.rm(ID);
}
Unread.readArray(Unread.postsQuotingYou);
if (Conf['Unread Line']) {
Unread.setLine();
} }
Unread.updatePosition();
Unread.setLine();
return Unread.update(); return Unread.update();
}, },
addPosts: function(posts) { addPost: function() {
var ID, post, _i, _len, _ref, _ref1; var _ref;
for (_i = 0, _len = posts.length; _i < _len; _i++) { if (this.isFetchedQuote || this.isClone) {
post = posts[_i]; return;
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);
} }
if (Conf['Unread Line']) { Unread.order.push(this);
Unread.setLine((_ref = (_ref1 = Unread.posts.first) != null ? _ref1.data : void 0, __indexOf.call(posts, _ref) >= 0)); 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(); Unread.posts.add(this.ID);
return Unread.update(); Unread.addPostQuotingYou(this);
return Unread.position != null ? Unread.position : Unread.position = Unread.order[this.ID];
}, },
addPostQuotingYou: function(post) { addPostQuotingYou: function(post) {
var quotelink, _i, _len, _ref; var quotelink, _i, _len, _ref, _ref1;
_ref = post.nodes.quotelinks; _ref = post.nodes.quotelinks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
quotelink = _ref[_i]; quotelink = _ref[_i];
if (!(QR.db.get(Get.postDataFromLink(quotelink)))) { if (!((_ref1 = QR.db) != null ? _ref1.get(Get.postDataFromLink(quotelink)) : void 0)) {
continue; continue;
} }
Unread.postsQuotingYou.push(post); Unread.postsQuotingYou.add(post.ID);
Unread.openNotification(post); Unread.openNotification(post);
return; return;
} }
@ -13242,8 +13239,8 @@
if (!Header.areNotificationsEnabled) { if (!Header.areNotificationsEnabled) {
return; return;
} }
notif = new Notification("" + (post.getNameBlock()) + " replied to you", { notif = new Notification("" + post.info.nameBlock + " replied to you", {
body: post.info.comment, body: post.info[Conf['Remove Spoilers'] || Conf['Reveal Spoilers'] ? 'comment' : 'commentSpoilered'],
icon: Favicon.logo icon: Favicon.logo
}); });
notif.onclick = function() { notif.onclick = function() {
@ -13257,80 +13254,83 @@
}; };
}, },
onUpdate: function(e) { onUpdate: function(e) {
if (e.detail[404]) { if (!e.detail[404]) {
return Unread.update(); Unread.setLine();
} else if (Conf['Quote Threading']) {
Unread.read(); 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(); return Unread.update();
}, },
readArray: function(arr) { readSinglePost: function(post) {
var i, post, _i, _len; var ID;
for (i = _i = 0, _len = arr.length; _i < _len; i = ++_i) { ID = post.ID;
post = arr[i]; if (!Unread.posts.has(ID)) {
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) {
return; return;
} }
posts = Unread.posts; Unread.posts["delete"](ID);
while (post = posts.first) { Unread.postsQuotingYou["delete"](ID);
if (!(Header.getBottomOf(post.data.nodes.root) > -1)) { 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; break;
} }
ID = post.ID, data = post.data; count++;
posts.rm(ID); Unread.posts["delete"](ID);
if (Conf['Mark Quotes of You'] && QR.db.get({ Unread.postsQuotingYou["delete"](ID);
if (Conf['Mark Quotes of You'] && ((_ref1 = QR.db) != null ? _ref1.get({
boardID: data.board.ID, boardID: data.board.ID,
threadID: data.thread.ID, threadID: data.thread.ID,
postID: ID postID: ID
})) { }) : void 0)) {
QuoteMarkers.lastRead = data.nodes.root; QuoteYou.lastRead = root;
} }
Unread.position = Unread.position.next;
} }
if (!ID) { if (!count) {
return; return;
} }
if (Unread.lastReadPost < ID) { Unread.updatePosition();
Unread.lastReadPost = ID;
}
Unread.saveLastReadPost(); Unread.saveLastReadPost();
Unread.readArray(Unread.postsQuotingYou);
if (e) { if (e) {
return Unread.update(); return Unread.update();
} }
}), }),
saveLastReadPost: $.debounce(5 * $.SECOND, function() { updatePosition: function() {
if (Unread.thread.isDead) { 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; return;
} }
Unread.db.forceSync();
return Unread.db.set({ return Unread.db.set({
boardID: Unread.thread.board.ID, boardID: Unread.thread.board.ID,
threadID: Unread.thread.ID, threadID: Unread.thread.ID,
@ -13338,27 +13338,39 @@
}); });
}), }),
setLine: function(force) { setLine: function(force) {
var post; if (!Conf['Unread Line']) {
if (!(d.hidden || force === true)) {
return; return;
} }
if (!(post = Unread.posts.first)) { if (d.hidden || (force === true)) {
return $.rm(Unread.hr); if (Unread.linePosition = Unread.positionPrev()) {
} $.after(Unread.linePosition.data.nodes.root, Unread.hr);
if ($.x('preceding-sibling::div[contains(@class,"replyContainer")]', post.data.nodes.root)) { } else {
return $.before(post.data.nodes.root, Unread.hr); $.rm(Unread.hr);
}
} }
return Unread.hr.hidden = Unread.linePosition === Unread.order.last;
}, },
update: function() { update: function() {
var count; var count, countQuotingYou, titleCount, titleDead, titleQuotingYou;
count = Unread.posts.length; count = Unread.posts.size;
countQuotingYou = Unread.postsQuotingYou.size;
if (Conf['Unread Count']) { 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']) { 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"]; 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); return $.add(d.head, Favicon.el);
} }
}; };

View File

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