Browse Source

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.
merge-requests/40/head
Deimos 6 years ago
parent
commit
05b8819767
  1. 34
      tildes/alembic/versions/afa3128a9b54_add_exemplary_comment_tag.py
  2. 11
      tildes/scss/_themes.scss
  3. 1
      tildes/scss/_variables.scss
  4. 4
      tildes/scss/modules/_btn.scss
  5. 25
      tildes/scss/modules/_comment.scss
  6. 6
      tildes/tildes/enums.py
  7. 4
      tildes/tildes/models/comment/comment.py
  8. 6
      tildes/tildes/models/comment/comment_tree.py
  9. 22
      tildes/tildes/templates/macros/comments.jinja2

34
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

11
tildes/scss/_themes.scss

@ -117,12 +117,11 @@
color: $text-secondary-color; 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 { %collapsed-theme {
header { header {

1
tildes/scss/_variables.scss

@ -36,6 +36,7 @@ $topic-tag-nsfw-color: $red;
$topic-tag-spoiler-color: $yellow; $topic-tag-spoiler-color: $yellow;
// Colors for comment tags // Colors for comment tags
$comment-tag-exemplary-color: $green;
$comment-tag-joke-color: $cyan; $comment-tag-joke-color: $cyan;
$comment-tag-noise-color: $yellow; $comment-tag-noise-color: $yellow;
$comment-tag-offtopic-color: $blue; $comment-tag-offtopic-color: $blue;

4
tildes/scss/modules/_btn.scss

@ -101,6 +101,10 @@
} }
} }
.btn-comment-tag-exemplary {
@include tagbutton($comment-tag-exemplary-color);
}
.btn-comment-tag-joke { .btn-comment-tag-joke {
@include tagbutton($comment-tag-joke-color); @include tagbutton($comment-tag-joke-color);
} }

25
tildes/scss/modules/_comment.scss

@ -57,6 +57,22 @@
margin-right: 0.4rem; 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 { .comment-nav-link {
font-size: 0.6rem; font-size: 0.6rem;
margin-left: 0.4rem; margin-left: 0.4rem;
@ -112,7 +128,7 @@
.comment-votes { .comment-votes {
font-size: 0.6rem; font-size: 0.6rem;
font-weight: bold; font-weight: bold;
margin: 0 0.4rem;
margin: 0.2rem 0.4rem;
} }
.is-comment-by-op { .is-comment-by-op {
@ -191,3 +207,10 @@
border-left: 3px solid $orange !important; border-left: 3px solid $orange !important;
} }
} }
.is-comment-exemplary {
& > .comment-itself {
margin-left: -2px;
border-left: 3px solid $comment-tag-exemplary-color !important;
}
}

6
tildes/tildes/enums.py

@ -40,6 +40,7 @@ class CommentSortOption(enum.Enum):
class CommentTagOption(enum.Enum): class CommentTagOption(enum.Enum):
"""Enum for the (site-wide) comment tag options.""" """Enum for the (site-wide) comment tag options."""
EXEMPLARY = enum.auto()
JOKE = enum.auto() JOKE = enum.auto()
OFFTOPIC = enum.auto() OFFTOPIC = enum.auto()
NOISE = enum.auto() NOISE = enum.auto()
@ -48,6 +49,11 @@ class CommentTagOption(enum.Enum):
@property @property
def reason_prompt(self) -> Optional[str]: def reason_prompt(self) -> Optional[str]:
"""Return the reason prompt for this tag, if any.""" """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": if self.name == "MALICE":
return "Why is this malicious? (required, will only be visible to admins)" return "Why is this malicious? (required, will only be visible to admins)"

4
tildes/tildes/models/comment/comment.py

@ -156,6 +156,10 @@ class Comment(DatabaseModel):
acl.append((Allow, Everyone, "view")) 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: # vote:
# - removed comments can't be voted on by anyone # - removed comments can't be voted on by anyone
# - otherwise, logged-in users except the author can vote # - otherwise, logged-in users except the author can vote

6
tildes/tildes/models/comment/comment_tree.py

@ -291,4 +291,10 @@ class CommentInTree(ObjectProxy):
if self.is_tag_active("joke"): if self.is_tag_active("joke"):
return (self.num_votes // 2,) 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,) return (self.num_votes,)

22
tildes/tildes/templates/macros/comments.jinja2

@ -87,13 +87,27 @@
{% if request.has_permission('view', comment) %} {% if request.has_permission('view', comment) %}
{# Show votes at the top only if it's your own comment #} {# Show votes at the top only if it's your own comment #}
{% if request.user == comment.user and comment.num_votes > 0 %} {% if request.user == comment.user and comment.num_votes > 0 %}
<span class="comment-votes">{{ comment.num_votes }}
<div class="comment-votes">{{ comment.num_votes }}
{% trans num_votes=comment.num_votes %} {% trans num_votes=comment.num_votes %}
vote vote
{% pluralize %} {% pluralize %}
votes votes
{% endtrans %} {% endtrans %}
</span>
</div>
{% endif %}
{% if (request.has_permission("view_exemplary_reasons", comment)
and comment.is_tag_active("exemplary")) %}
<details class="comment-exemplary-reasons">
<summary><span class="label label-comment-tag label-comment-tag-exemplary">Exemplary</span>
<span class="comment-tag-count">x{{ comment.tag_counts["exemplary"] }}</span>
</summary>
<ul>
{% for tag in comment.tags if tag.name == "exemplary" %}
<li>"{{ tag.reason }}"</li>
{% endfor %}
</ul>
</details>
{% endif %} {% endif %}
{% if comment.tag_counts and request.has_permission("view_tags", comment) %} {% if comment.tag_counts and request.has_permission("view_tags", comment) %}
@ -223,6 +237,10 @@
{% do classes.append('is-comment-by-op') %} {% do classes.append('is-comment-by-op') %}
{% endif %} {% 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" %} {% if comment.collapsed_state == "full" %}
{% do classes.append("is-comment-collapsed") %} {% do classes.append("is-comment-collapsed") %}
{% elif comment.collapsed_state == "individual" %} {% elif comment.collapsed_state == "individual" %}

Loading…
Cancel
Save