Handle count of notifications banner

This commit is contained in:
Joachim 2021-11-24 19:00:30 +01:00
parent 2ad37a22dd
commit db5e7a886a
5 changed files with 78 additions and 3 deletions

View file

@ -22,6 +22,11 @@ class ActivityStream(RedisStore):
stream_id = self.stream_id(user) stream_id = self.stream_id(user)
return f"{stream_id}-unread" return f"{stream_id}-unread"
def unread_by_status_type_id(self, user):
"""the redis key for this user's unread count for this stream"""
stream_id = self.stream_id(user)
return f"{stream_id}-unread-by-type"
def get_rank(self, obj): # pylint: disable=no-self-use def get_rank(self, obj): # pylint: disable=no-self-use
"""statuses are sorted by date published""" """statuses are sorted by date published"""
return obj.published_date.timestamp() return obj.published_date.timestamp()
@ -35,6 +40,10 @@ class ActivityStream(RedisStore):
for user in self.get_audience(status): for user in self.get_audience(status):
# add to the unread status count # add to the unread status count
pipeline.incr(self.unread_id(user)) pipeline.incr(self.unread_id(user))
# add to the unread status count for status type
pipeline.hincrby(
self.unread_by_status_type_id(user), get_status_type(status), 1
)
# and go! # and go!
pipeline.execute() pipeline.execute()
@ -55,6 +64,7 @@ class ActivityStream(RedisStore):
"""load the statuses to be displayed""" """load the statuses to be displayed"""
# clear unreads for this feed # clear unreads for this feed
r.set(self.unread_id(user), 0) r.set(self.unread_id(user), 0)
r.delete(self.unread_by_status_type_id(user))
statuses = self.get_store(self.stream_id(user)) statuses = self.get_store(self.stream_id(user))
return ( return (
@ -75,6 +85,14 @@ class ActivityStream(RedisStore):
"""get the unread status count for this user's feed""" """get the unread status count for this user's feed"""
return int(r.get(self.unread_id(user)) or 0) return int(r.get(self.unread_id(user)) or 0)
def get_unread_count_by_status_type(self, user):
"""get the unread status count for this user's feed's status types"""
status_types = r.hgetall(self.unread_by_status_type_id(user))
return {
str(key.decode("utf-8")): int(value) or 0
for key, value in status_types.items()
}
def populate_streams(self, user): def populate_streams(self, user):
"""go from zero to a timeline""" """go from zero to a timeline"""
self.populate_store(self.stream_id(user)) self.populate_store(self.stream_id(user))
@ -460,7 +478,7 @@ def remove_status_task(status_ids):
@app.task(queue=HIGH) @app.task(queue=HIGH)
def add_status_task(status_id, increment_unread=False): def add_status_task(status_id, increment_unread=False):
"""add a status to any stream it should be in""" """add a status to any stream it should be in"""
status = models.Status.objects.get(id=status_id) status = models.Status.objects.select_subclasses().get(id=status_id)
# we don't want to tick the unread count for csv import statuses, idk how better # we don't want to tick the unread count for csv import statuses, idk how better
# to check than just to see if the states is more than a few days old # to check than just to see if the states is more than a few days old
if status.created_date < timezone.now() - timedelta(days=2): if status.created_date < timezone.now() - timedelta(days=2):
@ -507,3 +525,20 @@ def handle_boost_task(boost_id):
stream.remove_object_from_related_stores(boosted, stores=audience) stream.remove_object_from_related_stores(boosted, stores=audience)
for status in old_versions: for status in old_versions:
stream.remove_object_from_related_stores(status, stores=audience) stream.remove_object_from_related_stores(status, stores=audience)
def get_status_type(status):
"""return status type even for boosted statuses"""
status_type = status.status_type.lower()
# Check if current status is a boost
if hasattr(status, "boost"):
# Act in accordance of your findings
if hasattr(status.boost.boosted_status, "review"):
status_type = "review"
if hasattr(status.boost.boosted_status, "comment"):
status_type = "comment"
if hasattr(status.boost.boosted_status, "quotation"):
status_type = "quotation"
return status_type

View file

@ -113,9 +113,42 @@ let BookWyrm = new class {
* @return {undefined} * @return {undefined}
*/ */
updateCountElement(counter, data) { updateCountElement(counter, data) {
let count = data.count;
const count_by_type = data.count_by_type;
const currentCount = counter.innerText; const currentCount = counter.innerText;
const count = data.count;
const hasMentions = data.has_mentions; const hasMentions = data.has_mentions;
const allowedStatusTypesEl = document.getElementById('unread-notifications-wrapper');
// If we're on the right counter element
if (counter.closest('[data-poll-wrapper]').contains(allowedStatusTypesEl)) {
const allowedStatusTypes = JSON.parse(allowedStatusTypesEl.textContent);
// For keys in common between allowedStatusTypes and count_by_type
// This concerns 'review', 'quotation', 'comment'
count = allowedStatusTypes.reduce(function(prev, currentKey) {
const currentValue = count_by_type[currentKey] | 0;
return prev + currentValue;
}, 0);
// Add all the "other" in count_by_type if 'everything' is allowed
if (allowedStatusTypes.includes('everything')) {
// Clone count_by_type with 0 for reviews/quotations/comments
const count_by_everything_else = Object.assign(
{},
count_by_type,
{review: 0, quotation: 0, comment: 0}
);
count = Object.keys(count_by_everything_else).reduce(
function(prev, currentKey) {
const currentValue =
count_by_everything_else[currentKey] | 0
return prev + currentValue;
},
count
);
}
}
if (count != currentCount) { if (count != currentCount) {
this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'is-hidden', count < 1); this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'is-hidden', count < 1);

View file

@ -54,6 +54,7 @@
{% if not activities.number > 1 %} {% if not activities.number > 1 %}
<a href="{{ request.path }}" class="transition-y is-hidden notification is-primary is-block" data-poll-wrapper> <a href="{{ request.path }}" class="transition-y is-hidden notification is-primary is-block" data-poll-wrapper>
{% blocktrans with tab_key=tab.key %}load <span data-poll="stream/{{ tab_key }}">0</span> unread status(es){% endblocktrans %} {% blocktrans with tab_key=tab.key %}load <span data-poll="stream/{{ tab_key }}">0</span> unread status(es){% endblocktrans %}
{{ allowed_status_types|json_script:"unread-notifications-wrapper" }}
</a> </a>
{% if request.user.show_goal and not goal and tab.key == 'home' %} {% if request.user.show_goal and not goal and tab.key == 'home' %}

View file

@ -59,6 +59,7 @@ class Feed(View):
"streams": STREAMS, "streams": STREAMS,
"goal_form": forms.GoalForm(), "goal_form": forms.GoalForm(),
"feed_status_types_options": FeedFilterChoices, "feed_status_types_options": FeedFilterChoices,
"allowed_status_types": request.user.feed_status_types,
"settings_saved": settings_saved, "settings_saved": settings_saved,
"path": f"/{tab['key']}", "path": f"/{tab['key']}",
}, },

View file

@ -22,4 +22,9 @@ def get_unread_status_count(request, stream="home"):
stream = activitystreams.streams.get(stream) stream = activitystreams.streams.get(stream)
if not stream: if not stream:
return JsonResponse({}) return JsonResponse({})
return JsonResponse({"count": stream.get_unread_count(request.user)}) return JsonResponse(
{
"count": stream.get_unread_count(request.user),
"count_by_type": stream.get_unread_count_by_status_type(request.user),
}
)