From 11e962c84853cc4c9502798e688ea9c24ababd76 Mon Sep 17 00:00:00 2001
From: Sam <sam.saffron@gmail.com>
Date: Tue, 11 Feb 2014 15:28:05 +1100
Subject: [PATCH] BUGFIX: improve quality of unread / new counters

---
 .../javascripts/discourse/lib/screen_track.js |  8 ++++++-
 .../discourse/models/topic_tracking_state.js  | 23 ++++++++++++++++---
 .../routes/discovery_route_builders.js        |  3 +++
 .../models/topic_tracking_state_test.js       | 21 +++++++++++++++++
 4 files changed, 51 insertions(+), 4 deletions(-)
 create mode 100644 test/javascripts/models/topic_tracking_state_test.js

diff --git a/app/assets/javascripts/discourse/lib/screen_track.js b/app/assets/javascripts/discourse/lib/screen_track.js
index 7d472fb65..91a6111f7 100644
--- a/app/assets/javascripts/discourse/lib/screen_track.js
+++ b/app/assets/javascripts/discourse/lib/screen_track.js
@@ -37,6 +37,11 @@ Discourse.ScreenTrack = Ember.Object.extend({
   },
 
   stop: function() {
+    if(!this.get('topicId')) {
+      // already stopped no need to "extra stop"
+      return;
+    }
+
     this.tick();
     this.flush();
     this.reset();
@@ -105,9 +110,10 @@ Discourse.ScreenTrack = Ember.Object.extend({
     var highestSeenByTopic = Discourse.Session.currentProp('highestSeenByTopic');
     if ((highestSeenByTopic[topicId] || 0) < highestSeen) {
       highestSeenByTopic[topicId] = highestSeen;
-      Discourse.TopicTrackingState.current().updateSeen(topicId, highestSeen);
     }
 
+    Discourse.TopicTrackingState.current().updateSeen(topicId, highestSeen);
+
     if (!$.isEmptyObject(newTimings)) {
       Discourse.ajax('/topics/timings', {
         data: {
diff --git a/app/assets/javascripts/discourse/models/topic_tracking_state.js b/app/assets/javascripts/discourse/models/topic_tracking_state.js
index 51700570d..d6b45d073 100644
--- a/app/assets/javascripts/discourse/models/topic_tracking_state.js
+++ b/app/assets/javascripts/discourse/models/topic_tracking_state.js
@@ -39,8 +39,9 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
   },
 
   updateSeen: function(topicId, highestSeen) {
+    if(!topicId || !highestSeen) { return; }
     var state = this.states["t" + topicId];
-    if(state && state.last_read_post_number < highestSeen) {
+    if(state && (!state.last_read_post_number || state.last_read_post_number < highestSeen)) {
       state.last_read_post_number = highestSeen;
       this.incrementMessageCount();
     }
@@ -84,9 +85,24 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
 
   sync: function(list, filter){
     var tracker = this;
+    var states = this.states;
 
     if(!list || !list.topics) { return; }
 
+    // compensate for delayed "new" topics
+    // client side we know they are not new, server side we think they are
+    for(var i=list.topics.length-1; i>=0; i--){
+      var state = states["t"+ list.topics[i].id];
+      if(state && state.last_read_post_number > 0){
+        if(filter === "new"){
+          list.topics.splice(i, 1);
+        } else {
+          list.topics[i].unseen = false;
+          list.topics[i].dont_sync = true;
+        }
+      }
+    }
+
     if(filter === "new" && !list.more_topics_url){
       // scrub all new rows and reload from list
       _.each(this.states, function(state){
@@ -112,10 +128,11 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
       if(topic.unseen) {
         row.last_read_post_number = null;
       } else if (topic.unread || topic.new_posts){
-        // subtle issue here
         row.last_read_post_number = topic.highest_post_number - ((topic.unread||0) + (topic.new_posts||0));
       } else {
-        delete tracker.states["t" + topic.id];
+        if(!topic.dont_sync) {
+          delete tracker.states["t" + topic.id];
+        }
         return;
       }
 
diff --git a/app/assets/javascripts/discourse/routes/discovery_route_builders.js b/app/assets/javascripts/discourse/routes/discovery_route_builders.js
index 86487c194..53e7ac19f 100644
--- a/app/assets/javascripts/discourse/routes/discovery_route_builders.js
+++ b/app/assets/javascripts/discourse/routes/discovery_route_builders.js
@@ -11,6 +11,9 @@ function buildTopicRoute(filter) {
     },
 
     model: function() {
+      // attempt to stop early cause we need this to be called before .sync
+      Discourse.ScreenTrack.current().stop();
+
       return Discourse.TopicList.list(filter).then(function(list) {
         var tracking = Discourse.TopicTrackingState.current();
         if (tracking) {
diff --git a/test/javascripts/models/topic_tracking_state_test.js b/test/javascripts/models/topic_tracking_state_test.js
new file mode 100644
index 000000000..305fc1d17
--- /dev/null
+++ b/test/javascripts/models/topic_tracking_state_test.js
@@ -0,0 +1,21 @@
+module("Discourse.TopicTrackingState");
+
+test("sync", function () {
+
+  var state = Discourse.TopicTrackingState.create();
+  // fake track it
+  state.states["t111"] = {last_read_post_number: null};
+
+  state.updateSeen(111, 7);
+  var list = {topics: [{
+    highest_post_number: null,
+    id: 111,
+    unread: 10,
+    new_posts: 10
+    }]};
+
+  state.sync(list, "new");
+
+  equal(list.topics.length, 0, "expect new topic to be removed as it was seen");
+
+});