From 05b88197673101a59c20a9a870653cfb86c3d085 Mon Sep 17 00:00:00 2001 From: Deimos Date: Thu, 20 Sep 2018 14:22:45 -0600 Subject: [PATCH] Add "exemplary" comment tag This adds an "exemplary" comment tag which has a few special behaviors: * Requires a reason, which is visible (anonymously) to the tagged comment's author. * Highlights the comment's left side in green * Acts as a multiplier to the comment's voting I think we'll still want to add a cooldown or something similar on using this tag, but this covers the basic functionality for it. --- .../afa3128a9b54_add_exemplary_comment_tag.py | 34 +++++++++++++++++++ tildes/scss/_themes.scss | 11 +++--- tildes/scss/_variables.scss | 1 + tildes/scss/modules/_btn.scss | 4 +++ tildes/scss/modules/_comment.scss | 25 +++++++++++++- tildes/tildes/enums.py | 6 ++++ tildes/tildes/models/comment/comment.py | 4 +++ tildes/tildes/models/comment/comment_tree.py | 6 ++++ .../tildes/templates/macros/comments.jinja2 | 22 ++++++++++-- 9 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 tildes/alembic/versions/afa3128a9b54_add_exemplary_comment_tag.py diff --git a/tildes/alembic/versions/afa3128a9b54_add_exemplary_comment_tag.py b/tildes/alembic/versions/afa3128a9b54_add_exemplary_comment_tag.py new file mode 100644 index 0000000..206239d --- /dev/null +++ b/tildes/alembic/versions/afa3128a9b54_add_exemplary_comment_tag.py @@ -0,0 +1,34 @@ +"""Add Exemplary comment tag + +Revision ID: afa3128a9b54 +Revises: 1ade2bf86efc +Create Date: 2018-09-18 22:17:39.619439 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "afa3128a9b54" +down_revision = "1ade2bf86efc" +branch_labels = None +depends_on = None + + +def upgrade(): + # ALTER TYPE doesn't work from inside a transaction, disable it + connection = None + if not op.get_context().as_sql: + connection = op.get_bind() + connection.execution_options(isolation_level="AUTOCOMMIT") + + op.execute("ALTER TYPE commenttagoption ADD VALUE IF NOT EXISTS 'EXEMPLARY'") + + # re-activate the transaction for any future migrations + if connection is not None: + connection.execution_options(isolation_level="READ_COMMITTED") + + +def downgrade(): + pass diff --git a/tildes/scss/_themes.scss b/tildes/scss/_themes.scss index 2667ffc..7040a09 100644 --- a/tildes/scss/_themes.scss +++ b/tildes/scss/_themes.scss @@ -117,12 +117,11 @@ color: $text-secondary-color; } - .comment-tags { - .label-comment-tag-joke { @include specialtag($comment-tag-joke-color, $is-light); } - .label-comment-tag-noise { @include specialtag($comment-tag-noise-color, $is-light); } - .label-comment-tag-offtopic { @include specialtag($comment-tag-offtopic-color, $is-light); } - .label-comment-tag-malice { @include specialtag($comment-tag-malice-color, $is-light); } - } + .label-comment-tag-exemplary { @include specialtag($comment-tag-exemplary-color, $is-light); } + .label-comment-tag-joke { @include specialtag($comment-tag-joke-color, $is-light); } + .label-comment-tag-noise { @include specialtag($comment-tag-noise-color, $is-light); } + .label-comment-tag-offtopic { @include specialtag($comment-tag-offtopic-color, $is-light); } + .label-comment-tag-malice { @include specialtag($comment-tag-malice-color, $is-light); } %collapsed-theme { header { diff --git a/tildes/scss/_variables.scss b/tildes/scss/_variables.scss index d33902e..59f6176 100644 --- a/tildes/scss/_variables.scss +++ b/tildes/scss/_variables.scss @@ -36,6 +36,7 @@ $topic-tag-nsfw-color: $red; $topic-tag-spoiler-color: $yellow; // Colors for comment tags +$comment-tag-exemplary-color: $green; $comment-tag-joke-color: $cyan; $comment-tag-noise-color: $yellow; $comment-tag-offtopic-color: $blue; diff --git a/tildes/scss/modules/_btn.scss b/tildes/scss/modules/_btn.scss index f978ce1..9d87a0e 100644 --- a/tildes/scss/modules/_btn.scss +++ b/tildes/scss/modules/_btn.scss @@ -101,6 +101,10 @@ } } +.btn-comment-tag-exemplary { + @include tagbutton($comment-tag-exemplary-color); +} + .btn-comment-tag-joke { @include tagbutton($comment-tag-joke-color); } diff --git a/tildes/scss/modules/_comment.scss b/tildes/scss/modules/_comment.scss index b7d106b..621ee6b 100644 --- a/tildes/scss/modules/_comment.scss +++ b/tildes/scss/modules/_comment.scss @@ -57,6 +57,22 @@ margin-right: 0.4rem; } +.comment-exemplary-reasons { + display: inline-block; + margin: 0.2rem 0.4rem; + font-size: 0.6rem; + + &[open] { + border-bottom: 1px dashed; + } + + ul { + list-style-type: circle; + margin-top: 0; + margin-left: 2rem; + } +} + .comment-nav-link { font-size: 0.6rem; margin-left: 0.4rem; @@ -112,7 +128,7 @@ .comment-votes { font-size: 0.6rem; font-weight: bold; - margin: 0 0.4rem; + margin: 0.2rem 0.4rem; } .is-comment-by-op { @@ -191,3 +207,10 @@ border-left: 3px solid $orange !important; } } + +.is-comment-exemplary { + & > .comment-itself { + margin-left: -2px; + border-left: 3px solid $comment-tag-exemplary-color !important; + } +} diff --git a/tildes/tildes/enums.py b/tildes/tildes/enums.py index 4a1bee5..2349d7d 100644 --- a/tildes/tildes/enums.py +++ b/tildes/tildes/enums.py @@ -40,6 +40,7 @@ class CommentSortOption(enum.Enum): class CommentTagOption(enum.Enum): """Enum for the (site-wide) comment tag options.""" + EXEMPLARY = enum.auto() JOKE = enum.auto() OFFTOPIC = enum.auto() NOISE = enum.auto() @@ -48,6 +49,11 @@ class CommentTagOption(enum.Enum): @property def reason_prompt(self) -> Optional[str]: """Return the reason prompt for this tag, if any.""" + if self.name == "EXEMPLARY": + return ( + "What makes this comment exemplary? " + "(required, visible to the comment's author anonymously)" + ) if self.name == "MALICE": return "Why is this malicious? (required, will only be visible to admins)" diff --git a/tildes/tildes/models/comment/comment.py b/tildes/tildes/models/comment/comment.py index ab4c5d9..14edb16 100644 --- a/tildes/tildes/models/comment/comment.py +++ b/tildes/tildes/models/comment/comment.py @@ -156,6 +156,10 @@ class Comment(DatabaseModel): acl.append((Allow, Everyone, "view")) + # view exemplary reasons: + # - only author gets shown the reasons (admins can see as well with all tags) + acl.append((Allow, self.user_id, "view_exemplary_reasons")) + # vote: # - removed comments can't be voted on by anyone # - otherwise, logged-in users except the author can vote diff --git a/tildes/tildes/models/comment/comment_tree.py b/tildes/tildes/models/comment/comment_tree.py index cc0311b..2d71dcb 100644 --- a/tildes/tildes/models/comment/comment_tree.py +++ b/tildes/tildes/models/comment/comment_tree.py @@ -291,4 +291,10 @@ class CommentInTree(ObjectProxy): if self.is_tag_active("joke"): return (self.num_votes // 2,) + # Exemplary comments add 1.0 to the the total weight of the exemplary tags, and + # multiply the vote count by that. At minimum (weight 1.0), votes are doubled. + if self.is_tag_active("exemplary"): + multiplier = self.tag_weights["exemplary"] + 1.0 + return (round(multiplier * self.num_votes),) + return (self.num_votes,) diff --git a/tildes/tildes/templates/macros/comments.jinja2 b/tildes/tildes/templates/macros/comments.jinja2 index 1560e1e..0848c76 100644 --- a/tildes/tildes/templates/macros/comments.jinja2 +++ b/tildes/tildes/templates/macros/comments.jinja2 @@ -87,13 +87,27 @@ {% if request.has_permission('view', comment) %} {# Show votes at the top only if it's your own comment #} {% if request.user == comment.user and comment.num_votes > 0 %} - {{ comment.num_votes }} +
{{ comment.num_votes }} {% trans num_votes=comment.num_votes %} vote {% pluralize %} votes {% endtrans %} - +
+ {% endif %} + + {% if (request.has_permission("view_exemplary_reasons", comment) + and comment.is_tag_active("exemplary")) %} +
+ Exemplary + x{{ comment.tag_counts["exemplary"] }} + +
    + {% for tag in comment.tags if tag.name == "exemplary" %} +
  • "{{ tag.reason }}"
  • + {% endfor %} +
+
{% endif %} {% if comment.tag_counts and request.has_permission("view_tags", comment) %} @@ -223,6 +237,10 @@ {% do classes.append('is-comment-by-op') %} {% endif %} + {% if request.has_permission('view', comment) and comment.is_tag_active("exemplary") %} + {% do classes.append("is-comment-exemplary") %} + {% endif %} + {% if comment.collapsed_state == "full" %} {% do classes.append("is-comment-collapsed") %} {% elif comment.collapsed_state == "individual" %}