diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js index 6ba3c099a..402155c53 100644 --- a/builds/appchan-x.user.js +++ b/builds/appchan-x.user.js @@ -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); } }; diff --git a/builds/crx/script.js b/builds/crx/script.js index 282549612..7c23bfdb1 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -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"]; } }; diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee index 594a923dc..017ed5132 100755 --- a/src/Monitoring/Unread.coffee +++ b/src/Monitoring/Unread.coffee @@ -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