diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index e1a52d263..4cba9939e 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -22,6 +22,11 @@ class ActivityStream(RedisStore): stream_id = self.stream_id(user) 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 """statuses are sorted by date published""" return obj.published_date.timestamp() @@ -35,6 +40,10 @@ class ActivityStream(RedisStore): for user in self.get_audience(status): # add to the unread status count 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! pipeline.execute() @@ -55,6 +64,7 @@ class ActivityStream(RedisStore): """load the statuses to be displayed""" # clear unreads for this feed r.set(self.unread_id(user), 0) + r.delete(self.unread_by_status_type_id(user)) statuses = self.get_store(self.stream_id(user)) return ( @@ -75,6 +85,14 @@ class ActivityStream(RedisStore): """get the unread status count for this user's feed""" 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): """go from zero to a timeline""" self.populate_store(self.stream_id(user)) @@ -460,7 +478,7 @@ def remove_status_task(status_ids): @app.task(queue=HIGH) def add_status_task(status_id, increment_unread=False): """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 # 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): @@ -507,3 +525,20 @@ def handle_boost_task(boost_id): stream.remove_object_from_related_stores(boosted, stores=audience) for status in old_versions: 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 diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 2d5b88adc..b45578000 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -113,9 +113,42 @@ let BookWyrm = new class { * @return {undefined} */ updateCountElement(counter, data) { + let count = data.count; + const count_by_type = data.count_by_type; const currentCount = counter.innerText; - const count = data.count; 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) { this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'is-hidden', count < 1); diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 0579610b6..1a2488afe 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -54,6 +54,7 @@ {% if not activities.number > 1 %} {% if request.user.show_goal and not goal and tab.key == 'home' %} diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index bd39b0834..7cf56d48f 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -59,6 +59,7 @@ class Feed(View): "streams": STREAMS, "goal_form": forms.GoalForm(), "feed_status_types_options": FeedFilterChoices, + "allowed_status_types": request.user.feed_status_types, "settings_saved": settings_saved, "path": f"/{tab['key']}", }, diff --git a/bookwyrm/views/updates.py b/bookwyrm/views/updates.py index 726145626..2bbc54776 100644 --- a/bookwyrm/views/updates.py +++ b/bookwyrm/views/updates.py @@ -22,4 +22,9 @@ def get_unread_status_count(request, stream="home"): stream = activitystreams.streams.get(stream) if not stream: 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), + } + )