Skip to content

Commit

Permalink
FEATURE: Publish read state on group messages. (#7989) [Undo revert] (#…
Browse files Browse the repository at this point in the history
…8024)

* Reenable: "FEATURE: Publish read state on group messages. (#7989)"

This reverts commit 67f5cc1.

* FIX: Read indicator only appears when the group setting is enabled
  • Loading branch information
romanrizzi committed Aug 20, 2019
1 parent 67f5cc1 commit 5dda5c2
Show file tree
Hide file tree
Showing 35 changed files with 531 additions and 21 deletions.
Expand Up @@ -40,7 +40,8 @@ export default MountWidget.extend({
"gaps",
"selectedQuery",
"selectedPostsCount",
"searchService"
"searchService",
"showReadIndicator"
);
},

Expand Down Expand Up @@ -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();
}
Expand Down
41 changes: 41 additions & 0 deletions app/assets/javascripts/discourse/components/topic-list-item.js.es6
Expand Up @@ -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/group-read/${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
Expand Down
11 changes: 11 additions & 0 deletions app/assets/javascripts/discourse/controllers/topic.js.es6
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion app/assets/javascripts/discourse/lib/transform-post.js.es6
Expand Up @@ -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]));
Expand Down
3 changes: 2 additions & 1 deletion app/assets/javascripts/discourse/models/group.js.es6
Expand Up @@ -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) {
Expand Down
Expand Up @@ -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>
Expand Down
Expand Up @@ -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 "far-eye"}}
</span>
{{~/if}}
</span>
<div class="link-bottom-line">
{{#unless hideCategory}}
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/discourse/templates/topic.hbs
Expand Up @@ -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")
Expand Down
86 changes: 82 additions & 4 deletions app/assets/javascripts/discourse/widgets/post-menu.js.es6
Expand Up @@ -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) {
return {
action: "toggleWhoRead",
title: "post.controls.read_indicator",
icon: "far-eye",
before: "read-count",
addContainer: false,
disabled
};
}
});

function likeCount(attrs) {
const count = attrs.likeCount;

Expand Down Expand Up @@ -341,7 +371,12 @@ export default createWidget("post-menu", {
},

defaultState() {
return { collapsed: true, likedUsers: [], adminVisible: false };
return {
collapsed: true,
likedUsers: [],
readers: [],
adminVisible: false
};
},

buildKey: attrs => `post-menu-${attrs.id}`,
Expand Down Expand Up @@ -508,6 +543,19 @@ export default createWidget("post-menu", {
);
}

if (state.readers.length) {
const remaining = state.totalReaders - state.readers.length;
contents.push(
this.attach("small-user-list", {
users: state.readers,
addSelf: false,
listClassName: "who-read",
description: "post.actions.people.read",
count: remaining
})
);
}

return contents;
},

Expand All @@ -525,9 +573,15 @@ export default createWidget("post-menu", {

showMoreActions() {
this.state.collapsed = false;
if (!this.state.likedUsers.length) {
return this.getWhoLiked();
}
const likesPromise = !this.state.likedUsers.length
? this.getWhoLiked()
: Ember.RSVP.resolve();

return likesPromise.then(() => {
if (!this.state.readers.length) {
return this.getWhoRead();
}
});
},

like() {
Expand Down Expand Up @@ -562,6 +616,12 @@ export default createWidget("post-menu", {
}
},

refreshReaders() {
if (this.state.readers.length) {
return this.getWhoRead();
}
},

getWhoLiked() {
const { attrs, state } = this;

Expand All @@ -576,12 +636,30 @@ export default createWidget("post-menu", {
});
},

getWhoRead() {
const { attrs, state } = this;

return this.store.find("post-reader", { id: attrs.id }).then(users => {
state.readers = users.map(avatarAtts);
state.totalReaders = users.totalRows;
});
},

toggleWhoLiked() {
const state = this.state;
if (state.likedUsers.length) {
state.likedUsers = [];
} else {
return this.getWhoLiked();
}
},

toggleWhoRead() {
const state = this.state;
if (this.state.readers.length) {
state.readers = [];
} else {
return this.getWhoRead();
}
}
});
Expand Up @@ -136,6 +136,7 @@ export default createWidget("post-stream", {
this.attach("post-small-action", transformed, { model: post })
);
} else {
transformed.showReadIndicator = attrs.showReadIndicator;
result.push(this.attach("post", transformed, { model: post }));
}

Expand Down
6 changes: 6 additions & 0 deletions app/assets/stylesheets/common/base/_topic-list.scss
Expand Up @@ -133,6 +133,12 @@
.raw-topic-link > * {
pointer-events: none;
}

.read-indicator {
&.unread {
display: none;
}
}
}

.link-bottom-line {
Expand Down
3 changes: 2 additions & 1 deletion app/assets/stylesheets/common/base/topic-post.scss
Expand Up @@ -641,7 +641,8 @@ blockquote > *:last-child {
font-size: $font-down-1;
}

.who-liked {
.who-liked,
.who-read {
transition: height 0.5s;
a {
margin: 0 0.25em 0.5em 0;
Expand Down
4 changes: 3 additions & 1 deletion app/assets/stylesheets/desktop/topic-post.scss
Expand Up @@ -66,6 +66,7 @@ nav.post-controls {
margin-left: 0;
margin-right: 0;
&.my-likes,
&.read-indicator,
&.regular-likes {
// Like count on posts
.d-icon {
Expand Down Expand Up @@ -838,7 +839,8 @@ a.attachment:before {
}
}

.who-liked {
.who-liked,
.who-read {
margin-top: 20px;
margin-bottom: 0;
width: 100%;
Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/mobile/topic-post.scss
Expand Up @@ -38,6 +38,7 @@ span.badge-posts {
flex: 0 1 auto;
button {
&.like,
&.read-indicator,
&.create-flag {
flex: 1 1 auto;
}
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/admin/groups_controller.rb
Expand Up @@ -153,7 +153,8 @@ def group_params
:default_notification_level,
:membership_request_template,
:owner_usernames,
:usernames
:usernames,
:publish_read_state
]
custom_fields = Group.editable_group_custom_fields
permitted << { custom_fields: custom_fields } unless custom_fields.blank?
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/groups_controller.rb
Expand Up @@ -552,7 +552,8 @@ def group_params(automatic: false)
:name,
:grant_trust_level,
:automatic_membership_email_domains,
:automatic_membership_retroactive
:automatic_membership_retroactive,
:publish_read_state
])

custom_fields = Group.editable_group_custom_fields
Expand Down
26 changes: 26 additions & 0 deletions app/controllers/post_readers_controller.rb
@@ -0,0 +1,26 @@
# frozen_string_literal: true

class PostReadersController < ApplicationController
requires_login

def index
post = Post.includes(topic: %i[allowed_groups]).find(params[:id])
read_state = post.topic.allowed_groups.any? { |g| g.publish_read_state? && g.users.include?(current_user) }
raise Discourse::InvalidAccess unless read_state

readers = User
.joins(:topic_users)
.where('topic_users.topic_id = ? AND COALESCE(topic_users.last_read_post_number, 1) >= ?', post.topic_id, post.post_number)
.where.not(id: [current_user.id, post.user_id])

readers = readers.map do |r|
{
id: r.id, avatar_template: r.avatar_template,
username: r.username,
username_lower: r.username_lower
}
end

render_json_dump(post_readers: readers)
end
end
1 change: 1 addition & 0 deletions app/models/group.rb
Expand Up @@ -897,6 +897,7 @@ def enqueue_update_mentions_job
# visibility_level :integer default(0), not null
# public_exit :boolean default(FALSE), not null
# public_admission :boolean default(FALSE), not null
# publish_read_state :boolean default(FALSE), not null
# membership_request_template :text
# messageable_level :integer default(0)
# mentionable_level :integer default(0)
Expand Down

1 comment on commit 5dda5c2

@discoursereviewbot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.