mirror of
https://github.com/discourse/discourse.git
synced 2025-01-18 14:03:18 +08:00
FEATURE: live reply indicators at the bottom of topic
This commit is contained in:
parent
ea50f823cb
commit
ae0acfb1df
|
@ -226,6 +226,8 @@
|
|||
{{signup-cta}}
|
||||
{{else}}
|
||||
{{#if currentUser}}
|
||||
{{plugin-outlet name="topic-above-footer-buttons" args=(hash model=model)}}
|
||||
|
||||
{{topic-footer-buttons
|
||||
topic=model
|
||||
toggleMultiSelect=(action "toggleMultiSelect")
|
||||
|
|
|
@ -59,8 +59,10 @@ export default Ember.Component.extend({
|
|||
this.set('presenceState', stateObject);
|
||||
},
|
||||
|
||||
_ACTIONS: ['edit', 'reply'],
|
||||
|
||||
shouldSharePresence(action){
|
||||
return ['edit','reply'].includes(action);
|
||||
return this._ACTIONS.includes(action);
|
||||
},
|
||||
|
||||
@observes('presenceState')
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { observes, on } from 'ember-addons/ember-computed-decorators';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
topicId: null,
|
||||
|
||||
messageBusChannel: null,
|
||||
presenceUsers: null,
|
||||
|
||||
@on('didInsertElement')
|
||||
_inserted() {
|
||||
this.set("presenceUsers", []);
|
||||
|
||||
ajax(`/presence/ping/${this.get("topicId")}`).then((data) => {
|
||||
this.setProperties({
|
||||
messageBusChannel: data.messagebus_channel,
|
||||
presenceUsers: data.users,
|
||||
});
|
||||
this.messageBus.subscribe(data.messagebus_channel, message => {
|
||||
console.log(message)
|
||||
this.set("presenceUsers", message.users);
|
||||
}, data.messagebus_id);
|
||||
});
|
||||
},
|
||||
|
||||
@on('willDestroyElement')
|
||||
_destroyed() {
|
||||
if (this.get("messageBusChannel")) {
|
||||
this.messageBus.unsubscribe(this.get("messageBusChannel"));
|
||||
this.set("messageBusChannel", null);
|
||||
}
|
||||
},
|
||||
|
||||
@computed('presenceUsers', 'currentUser.id')
|
||||
users(presenceUsers, currentUser_id){
|
||||
return (presenceUsers || []).filter(user => user.id !== currentUser_id);
|
||||
},
|
||||
|
||||
@computed('users.length')
|
||||
shouldDisplay(length){
|
||||
return length > 0;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
{{#if shouldDisplay}}
|
||||
<div class="presence-users">
|
||||
{{#each users as |user|}}
|
||||
{{avatar user avatarTemplatePath="avatar_template" usernamePath="username" imageSize="small"}}
|
||||
{{/each}}
|
||||
|
||||
<span class="presence-text">
|
||||
<span class="description">{{i18n 'presence.replying_to_topic' count=users.length}}</span>{{!-- (using comment to stop whitespace)
|
||||
--}}<span class="wave"><span class="dot">.</span><span class="dot">.</span><span class="dot">.</span></span>
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -0,0 +1 @@
|
|||
{{topic-presence-display topicId=model.id}}
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
shouldRender(_, ctx) {
|
||||
return ctx.siteSettings.presence_enabled;
|
||||
}
|
||||
};
|
|
@ -1,13 +1,9 @@
|
|||
.presence-users{
|
||||
.presence-users {
|
||||
background-color: $secondary;
|
||||
color: $primary-medium;
|
||||
padding: 0px 5px;
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right: 35px;
|
||||
|
||||
.wave {
|
||||
|
||||
.dot {
|
||||
display: inline-block;
|
||||
animation: wave 1.8s linear infinite;
|
||||
|
@ -33,10 +29,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.mobile-view .presence-users{
|
||||
.composer-fields .presence-users {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right: 35px;
|
||||
}
|
||||
|
||||
.mobile-view {
|
||||
.composer-fields .presence-users {
|
||||
top: 3px;
|
||||
right: 54px;
|
||||
.description{
|
||||
.description {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,3 +3,6 @@ en:
|
|||
presence:
|
||||
replying: "replying"
|
||||
editing: "editing"
|
||||
replying_to_topic:
|
||||
one: "is replying"
|
||||
other: "are replying"
|
||||
|
|
|
@ -29,44 +29,31 @@ after_initialize do
|
|||
end
|
||||
|
||||
def self.add(type, id, user_id)
|
||||
redis_key = get_redis_key(type, id)
|
||||
response = $redis.hset(redis_key, user_id, Time.zone.now)
|
||||
|
||||
response # Will be true if a new key
|
||||
# return true if a key was added
|
||||
$redis.hset(get_redis_key(type, id), user_id, Time.zone.now)
|
||||
end
|
||||
|
||||
def self.remove(type, id, user_id)
|
||||
redis_key = get_redis_key(type, id)
|
||||
response = $redis.hdel(redis_key, user_id)
|
||||
|
||||
response > 0 # Return true if key was actually deleted
|
||||
# return true if a key was deleted
|
||||
$redis.hdel(get_redis_key(type, id), user_id) > 0
|
||||
end
|
||||
|
||||
def self.get_users(type, id)
|
||||
redis_key = get_redis_key(type, id)
|
||||
user_ids = $redis.hkeys(redis_key).map(&:to_i)
|
||||
|
||||
user_ids = $redis.hkeys(get_redis_key(type, id)).map(&:to_i)
|
||||
# TODO: limit the # of users returned
|
||||
User.where(id: user_ids)
|
||||
end
|
||||
|
||||
def self.publish(type, id)
|
||||
topic =
|
||||
if type == 'post'
|
||||
Post.find_by(id: id).topic
|
||||
else
|
||||
Topic.find_by(id: id)
|
||||
end
|
||||
|
||||
users = get_users(type, id)
|
||||
serialized_users = users.map { |u| BasicUserSerializer.new(u, root: false) }
|
||||
message = {
|
||||
users: serialized_users
|
||||
}
|
||||
|
||||
message = { users: serialized_users }
|
||||
messagebus_channel = get_messagebus_channel(type, id)
|
||||
|
||||
topic = type == 'post' ? Post.find_by(id: id).topic : Topic.find_by(id: id)
|
||||
|
||||
if topic.archetype == Archetype.private_message
|
||||
user_ids = User.where('admin or moderator').pluck(:id)
|
||||
user_ids += topic.allowed_users.pluck(:id)
|
||||
user_ids = User.where('admin OR moderator').pluck(:id) + topic.allowed_users.pluck(:id)
|
||||
MessageBus.publish(messagebus_channel, message.as_json, user_ids: user_ids)
|
||||
else
|
||||
MessageBus.publish(messagebus_channel, message.as_json, group_ids: topic.secure_group_ids)
|
||||
|
@ -76,19 +63,17 @@ after_initialize do
|
|||
end
|
||||
|
||||
def self.cleanup(type, id)
|
||||
hash = $redis.hgetall(get_redis_key(type, id))
|
||||
original_hash_size = hash.length
|
||||
|
||||
any_changes = false
|
||||
has_changed = false
|
||||
|
||||
# Delete entries older than 20 seconds
|
||||
hash = $redis.hgetall(get_redis_key(type, id))
|
||||
hash.each do |user_id, time|
|
||||
if Time.zone.now - Time.parse(time) >= 20
|
||||
any_changes ||= remove(type, id, user_id)
|
||||
has_changed |= remove(type, id, user_id)
|
||||
end
|
||||
end
|
||||
|
||||
any_changes
|
||||
has_changed
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -99,6 +84,8 @@ after_initialize do
|
|||
requires_plugin PLUGIN_NAME
|
||||
before_action :ensure_logged_in
|
||||
|
||||
ACTIONS = %w{edit reply}.each(&:freeze)
|
||||
|
||||
def publish
|
||||
data = params.permit(
|
||||
:response_needed,
|
||||
|
@ -108,60 +95,38 @@ after_initialize do
|
|||
|
||||
payload = {}
|
||||
|
||||
if data[:previous] && data[:previous][:action].in?(['edit', 'reply'])
|
||||
if data[:previous] && data[:previous][:action].in?(ACTIONS)
|
||||
type = data[:previous][:post_id] ? 'post' : 'topic'
|
||||
id = data[:previous][:post_id] ? data[:previous][:post_id] : data[:previous][:topic_id]
|
||||
|
||||
topic =
|
||||
if type == 'post'
|
||||
Post.find_by(id: id)&.topic
|
||||
else
|
||||
Topic.find_by(id: id)
|
||||
end
|
||||
topic = type == 'post' ? Post.find_by(id: id)&.topic : Topic.find_by(id: id)
|
||||
|
||||
if topic
|
||||
guardian.ensure_can_see!(topic)
|
||||
|
||||
removed = Presence::PresenceManager.remove(type, id, current_user.id)
|
||||
any_removed = Presence::PresenceManager.cleanup(type, id)
|
||||
any_changes = removed || any_removed
|
||||
|
||||
users = Presence::PresenceManager.publish(type, id) if any_changes
|
||||
cleaned = Presence::PresenceManager.cleanup(type, id)
|
||||
users = Presence::PresenceManager.publish(type, id) if removed || cleaned
|
||||
end
|
||||
end
|
||||
|
||||
if data[:current] && data[:current][:action].in?(['edit', 'reply'])
|
||||
if data[:current] && data[:current][:action].in?(ACTIONS)
|
||||
type = data[:current][:post_id] ? 'post' : 'topic'
|
||||
id = data[:current][:post_id] ? data[:current][:post_id] : data[:current][:topic_id]
|
||||
|
||||
topic =
|
||||
if type == 'post'
|
||||
Post.find_by(id: id)&.topic
|
||||
else
|
||||
Topic.find_by(id: id)
|
||||
end
|
||||
topic = type == 'post' ? Post.find_by(id: id)&.topic : Topic.find_by(id: id)
|
||||
|
||||
if topic
|
||||
guardian.ensure_can_see!(topic)
|
||||
|
||||
added = Presence::PresenceManager.add(type, id, current_user.id)
|
||||
any_removed = Presence::PresenceManager.cleanup(type, id)
|
||||
any_changes = added || any_removed
|
||||
|
||||
users = Presence::PresenceManager.publish(type, id) if any_changes
|
||||
cleaned = Presence::PresenceManager.cleanup(type, id)
|
||||
users = Presence::PresenceManager.publish(type, id) if added || cleaned
|
||||
|
||||
if data[:response_needed]
|
||||
users ||= Presence::PresenceManager.get_users(type, id)
|
||||
|
||||
serialized_users = users.map { |u| BasicUserSerializer.new(u, root: false) }
|
||||
|
||||
messagebus_channel = Presence::PresenceManager.get_messagebus_channel(type, id)
|
||||
|
||||
payload = {
|
||||
messagebus_channel: messagebus_channel,
|
||||
messagebus_id: MessageBus.last_id(messagebus_channel),
|
||||
users: serialized_users
|
||||
}
|
||||
users ||= Presence::PresenceManager.get_users(type, id)
|
||||
payload = json_payload(messagebus_channel, users)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -169,10 +134,30 @@ after_initialize do
|
|||
render json: payload
|
||||
end
|
||||
|
||||
def ping
|
||||
topic_id = params.require(:topic_id)
|
||||
|
||||
Presence::PresenceManager.cleanup("topic", topic_id)
|
||||
|
||||
messagebus_channel = Presence::PresenceManager.get_messagebus_channel("topic", topic_id)
|
||||
users = Presence::PresenceManager.get_users("topic", topic_id)
|
||||
|
||||
render json: json_payload(messagebus_channel, users)
|
||||
end
|
||||
|
||||
def json_payload(channel, users)
|
||||
{
|
||||
messagebus_channel: channel,
|
||||
messagebus_id: MessageBus.last_id(channel),
|
||||
users: users.map { |u| BasicUserSerializer.new(u, root: false) }
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Presence::Engine.routes.draw do
|
||||
post '/publish' => 'presences#publish'
|
||||
get '/ping/:topic_id' => 'presences#ping'
|
||||
end
|
||||
|
||||
Discourse::Application.routes.append do
|
||||
|
|
Loading…
Reference in New Issue
Block a user