FEATURE: Publish read state on group messages. (Originally introduced in #7989) (#8025)

FEATURE: Publish read state on group messages. (Originally introduced in #7989) (#8025)

  • Revert “Revert “FEATURE: Publish read state on group messages. (#7989) [Undo revert] (#8024)”” This reverts commit 36425eb9f04cfac7201632d648050cb43a035bc1.

  • Fix: Show who read only if the attribute is enabled

  • PERF: Precalculate the last post readed by a group member

  • Use book-reader icon instear of far-eye

  • FIX: update topic groups correctly

  • DEV: Tidy up read indicator update on write

diff --git a/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6 b/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6
index 4121a8b..89a4812 100644
--- a/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6
+++ b/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6
@@ -40,7 +40,8 @@ export default MountWidget.extend({
       "gaps",
       "selectedQuery",
       "selectedPostsCount",
-      "searchService"
+      "searchService",
+      "showReadIndicator"
     );
   },
 
@@ -291,6 +292,12 @@ export default MountWidget.extend({
             onRefresh: "refreshLikes"
           });
         }
+
+        if (args.refreshReaders) {
+          this.dirtyKeys.keyDirty(`post-menu-${args.id}`, {
+            onRefresh: "refreshReaders"
+          });
+        }
       } else if (args.force) {
         this.dirtyKeys.forceAll();
       }
diff --git a/app/assets/javascripts/discourse/components/topic-list-item.js.es6 b/app/assets/javascripts/discourse/components/topic-list-item.js.es6
index 564938e..e7ed618 100644
--- a/app/assets/javascripts/discourse/components/topic-list-item.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-list-item.js.es6
@@ -35,6 +35,47 @@ export const ListItemDefaults = {
   attributeBindings: ["data-topic-id"],
   "data-topic-id": Ember.computed.alias("topic.id"),
 
+  didInsertElement() {
+    this._super(...arguments);
+
+    if (this.includeReadIndicator) {
+      this.messageBus.subscribe(this.readIndicatorChannel, data => {
+        const nodeClassList = document.querySelector(
+          `.indicator-topic-${data.topic_id}`
+        ).classList;
+
+        if (data.show_indicator) {
+          nodeClassList.remove("unread");
+        } else {
+          nodeClassList.add("unread");
+        }
+      });
+    }
+  },
+
+  willDestroyElement() {
+    this._super(...arguments);
+
+    if (this.includeReadIndicator) {
+      this.messageBus.unsubscribe(this.readIndicatorChannel);
+    }
+  },
+
+  @computed("topic.id")
+  readIndicatorChannel(topicId) {
+    return `/private-messages/read-indicator/${topicId}`;
+  },
+
+  @computed("topic.read_by_group_member")
+  unreadClass(readByGroupMember) {
+    return readByGroupMember ? "" : "unread";
+  },
+
+  @computed("topic.read_by_group_member")
+  includeReadIndicator(readByGroupMember) {
+    return typeof readByGroupMember !== "undefined";
+  },
+
   @computed
   newDotText() {
     return this.currentUser && this.currentUser.trust_level > 0
diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6
index c1983bc..25a635b 100644
--- a/app/assets/javascripts/discourse/controllers/topic.js.es6
+++ b/app/assets/javascripts/discourse/controllers/topic.js.es6
@@ -1348,6 +1348,17 @@ export default Ember.Controller.extend(bufferedProperty("model"), {
               })
               .then(() => refresh({ id: data.id, refreshLikes: true }));
             break;
+          case "read":
+            postStream
+              .triggerChangedPost(data.id, data.updated_at, {
+                preserveCooked: true
+              })
+              .then(() =>
+                refresh({
+                  id: data.id,
+                  refreshReaders: topic.show_read_indicator
+                })
+              );
           case "revised":
           case "rebaked": {
             postStream
diff --git a/app/assets/javascripts/discourse/lib/transform-post.js.es6 b/app/assets/javascripts/discourse/lib/transform-post.js.es6
index 7a1872b..5f882d8 100644
--- a/app/assets/javascripts/discourse/lib/transform-post.js.es6
+++ b/app/assets/javascripts/discourse/lib/transform-post.js.es6
@@ -71,7 +71,8 @@ export function transformBasicPost(post) {
     expandablePost: false,
     replyCount: post.reply_count,
     locked: post.locked,
-    userCustomFields: post.user_custom_fields
+    userCustomFields: post.user_custom_fields,
+    readCount: post.readers_count
   };
 
   _additionalAttributes.forEach(a => (postAtts[a] = post[a]));
diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6
index 5ea8f11..0489835 100644
--- a/app/assets/javascripts/discourse/models/group.js.es6
+++ b/app/assets/javascripts/discourse/models/group.js.es6
@@ -178,7 +178,8 @@ const Group = RestModel.extend({
       allow_membership_requests: this.allow_membership_requests,
       full_name: this.full_name,
       default_notification_level: this.default_notification_level,
-      membership_request_template: this.membership_request_template
+      membership_request_template: this.membership_request_template,
+      publish_read_state: this.publish_read_state
     };
 
     if (!this.id) {
diff --git a/app/assets/javascripts/discourse/templates/components/groups-form-interaction-fields.hbs b/app/assets/javascripts/discourse/templates/components/groups-form-interaction-fields.hbs
index b2f254c..959e2e1 100644
--- a/app/assets/javascripts/discourse/templates/components/groups-form-interaction-fields.hbs
+++ b/app/assets/javascripts/discourse/templates/components/groups-form-interaction-fields.hbs
@@ -52,6 +52,16 @@
       class="groups-form-messageable-level"}}
 </div>
 
+<div class="control-group">
+  <label>
+    {{input type="checkbox"
+          checked=model.publish_read_state
+          class="groups-form-publish-read-state"}}
+
+    {{i18n 'admin.groups.manage.interaction.publish_read_state'}}
+  </label>
+</div>
+
 {{#if showEmailSettings}}
   <div class="control-group">
     <label class="control-label">{{i18n 'admin.groups.manage.interaction.email'}}</label>
diff --git a/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs b/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs
index 6b6b7b2..f6d2ddf 100644
--- a/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs
+++ b/app/assets/javascripts/discourse/templates/list/topic-list-item.raw.hbs
@@ -23,6 +23,11 @@
     {{~#if showTopicPostBadges}}
     {{~raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}}
     {{~/if}}
+    {{~#if includeReadIndicator}}
+      <span class='read-indicator indicator-topic-{{topic.id}} {{unreadClass}}'>
+        {{~d-icon "book-reader"}}
+      </span>
+    {{~/if}}
   </span>
   <div class="link-bottom-line">
     {{#unless hideCategory}}
diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs
index f8e5ee3..c10ec5d 100644
--- a/app/assets/javascripts/discourse/templates/topic.hbs
+++ b/app/assets/javascripts/discourse/templates/topic.hbs
@@ -177,6 +177,7 @@
                   selectedPostsCount=selectedPostsCount
                   selectedQuery=selectedQuery
                   gaps=model.postStream.gaps
+                  showReadIndicator=model.show_read_indicator
                   showFlags=(action "showPostFlags")
                   editPost=(action "editPost")
                   showHistory=(route-action "showHistory")
diff --git a/app/assets/javascripts/discourse/widgets/post-menu.js.es6 b/app/assets/javascripts/discourse/widgets/post-menu.js.es6
index 8739675..db2ac3b 100644
--- a/app/assets/javascripts/discourse/widgets/post-menu.js.es6
+++ b/app/assets/javascripts/discourse/widgets/post-menu.js.es6
@@ -52,6 +52,36 @@ export function buildButton(name, widget) {
   }
 }
 
+registerButton("read-count", attrs => {
+  if (attrs.showReadIndicator) {
+    const count = attrs.readCount;
+    if (count > 0) {
+      return {
+        action: "toggleWhoRead",
+        title: "post.controls.read_indicator",
+        className: "button-count read-indicator",
+        contents: count,
+        iconRight: true,
+        addContainer: false
+      };
+    }
+  }
+});
+
+registerButton("read", attrs => {
+  const disabled = attrs.readCount === 0;
+  if (attrs.showReadIndicator) {

[... diff too long, it was truncated ...]

GitHub sha: 7c741fa0

FIX: don't blow up if the topic does not exists anymore

This commit has been mentioned on Discourse Meta. There might be relevant details there: