diff --git a/tildes/tildes/enums.py b/tildes/tildes/enums.py index bc294bb..4648783 100644 --- a/tildes/tildes/enums.py +++ b/tildes/tildes/enums.py @@ -20,6 +20,7 @@ class CommentSortOption(enum.Enum): VOTES = enum.auto() NEWEST = enum.auto() POSTED = enum.auto() + RELEVANCE = enum.auto() @property def description(self) -> str: @@ -28,6 +29,8 @@ class CommentSortOption(enum.Enum): return "newest first" elif self.name == "POSTED": return "order posted" + elif self.name == "RELEVANCE": + return "relevance" return "most {}".format(self.name.lower()) diff --git a/tildes/tildes/models/comment/comment.py b/tildes/tildes/models/comment/comment.py index 1283cc7..ab4c5d9 100644 --- a/tildes/tildes/models/comment/comment.py +++ b/tildes/tildes/models/comment/comment.py @@ -250,3 +250,18 @@ class Comment(DatabaseModel): def tags_by_user(self, user: User) -> Sequence[str]: """Return list of tag names that a user has applied to this comment.""" return [tag.name for tag in self.tags if tag.user_id == user.user_id] + + def is_tag_active(self, tag_name: str) -> bool: + """Return whether a tag has been applied enough to be considered "active".""" + tag_weight = self.tag_weights[tag_name] + + # all tags must have at least 1.0 weight + if tag_weight < 1.0: + return False + + # for "noise", weight must be more than 1/5 of the vote count (5 votes + # effectively override 1.0 of tag weight) + if tag_name == "noise" and self.num_votes >= tag_weight * 5: + return False + + return True diff --git a/tildes/tildes/models/comment/comment_tree.py b/tildes/tildes/models/comment/comment_tree.py index 849b4bc..6e1b148 100644 --- a/tildes/tildes/models/comment/comment_tree.py +++ b/tildes/tildes/models/comment/comment_tree.py @@ -4,7 +4,7 @@ """Contains the CommentTree and CommentInTree classes.""" from datetime import datetime -from typing import Iterator, List, Optional, Sequence +from typing import Iterator, List, Optional, Sequence, Tuple from prometheus_client import Histogram from wrapt import ObjectProxy @@ -98,6 +98,8 @@ class CommentTree: tree = sorted(tree, key=lambda c: c.created_time) elif sort == CommentSortOption.VOTES: tree = sorted(tree, key=lambda c: c.num_votes, reverse=True) + elif sort == CommentSortOption.RELEVANCE: + tree = sorted(tree, key=lambda c: c.relevance_sorting_value, reverse=True) for comment in tree: if not comment.has_visible_descendant: @@ -177,11 +179,7 @@ class CommentTree: if comment.user == self.viewer: continue - # Collapse a comment if it has weight from noise tags of at least - # 1.0 and the vote count is less than 5x the weight (so 5 votes are - # "stronger" than each 1.0 of noise and will prevent collapsing) - noise_weight = comment.tag_weights["noise"] - if noise_weight >= 1.0 and comment.num_votes < noise_weight * 5: + if comment.is_tag_active("noise"): comment.collapsed_state = "full" def uncollapse_new_comments(self, threshold: datetime) -> None: @@ -274,3 +272,26 @@ class CommentInTree(ObjectProxy): for reply in self.replies: reply.recursively_collapse() + + @property + def relevance_sorting_value(self) -> Tuple[int, ...]: + """Value to use for the comment with the "relevance" comment sorting method. + + Returns a tuple, which allows sorting the comments into "tiers" and then still + supporting further sorting inside those tiers when it's useful. For example, + comments tagged as offtopic can be sorted below all non-offtopic comments, but + then still sorted by votes relative to other offtopic comments. + """ + if self.is_removed: + return (-100,) + + if self.is_tag_active("noise"): + return (-2, self.num_votes) + + if self.is_tag_active("offtopic"): + return (-1, self.num_votes) + + if self.is_tag_active("joke"): + return (self.num_votes // 2,) + + return (self.num_votes,) diff --git a/tildes/tildes/views/topic.py b/tildes/tildes/views/topic.py index d7257e4..16d2997 100644 --- a/tildes/tildes/views/topic.py +++ b/tildes/tildes/views/topic.py @@ -243,7 +243,7 @@ def get_new_topic_form(request: Request) -> dict: @view_config(route_name="topic", renderer="topic.jinja2") -@use_kwargs({"comment_order": Enum(CommentSortOption, missing="votes")}) +@use_kwargs({"comment_order": Enum(CommentSortOption, missing="relevance")}) def get_topic(request: Request, comment_order: CommentSortOption) -> dict: """View a single topic.""" topic = request.context