Skip to content

Commit

Permalink
Rebuilt comment and activity
Browse files Browse the repository at this point in the history
  • Loading branch information
theskumar committed Aug 26, 2024
1 parent 0b08aaa commit eec6ead
Show file tree
Hide file tree
Showing 27 changed files with 515 additions and 492 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"pytestmark",
"ratelimit",
"SIGNUP",
"svgwrite",
"WAGTAILADMIN",
"wagtailcore"
]
Expand Down
34 changes: 33 additions & 1 deletion hypha/apply/activity/services.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
from django.utils import timezone

from .models import Activity


def edit_comment(activity: Activity, message: str) -> Activity:
"""
Edit a comment by creating a clone of the original comment with the updated message.
Args:
activity (Activity): The original comment activity to be edited.
message (str): The new message to replace the original comment's message.
Returns:
Activity: The edited comment activity with the updated message.
"""
if message == activity.message:
return activity

# Create a clone of the comment to edit
previous = Activity.objects.get(pk=activity.pk)
previous.pk = None
previous.current = False
previous.save()

activity.previous = previous
activity.edited = timezone.now()
activity.message = message
activity.current = True
activity.save()

return activity


def get_related_actions_for_user(obj, user):
"""Return Activity objects related to an object, esp. useful with
ApplicationSubmission and Project.
Expand Down Expand Up @@ -38,7 +69,8 @@ def get_related_comments_for_user(obj, user):
related_query = type(obj).activities.rel.related_query_name

return (
Activity.comments.filter(**{related_query: obj})
Activity.objects.filter(**{related_query: obj})
.exclude(current=False)
.select_related("user")
.prefetch_related(
"related_object",
Expand Down
41 changes: 41 additions & 0 deletions hypha/apply/activity/templates/activity/edit_comment.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{% load i18n %}

<form
action="{{ request.path }}"
method="POST"
hx-post="{{ request.path }}"
hx-target="this"
hx-swap="outerHTML"
class="form"
>
{% csrf_token %}
<div class="mb-4">
<div id="wmd-button-bar-edit-comment-{{ activity.id }}" class="wmd-button-bar not-prose"></div>

<textarea
id="wmd-input-edit-comment-{{ activity.id }}"
class="wmd-input w-full not-prose"
rows="10" name="message"
>{{ activity.message }}</textarea>

<div id="wmd-preview-edit-comment-{{ activity.id }}" class="w-full bg-gray-100 p-4 prose max-w-none shadow-sm"></div>
</div>

<div class="flex gap-2">
<button class="button button--primary" role="button" type="submit">{% trans "Submit" %}</button>
<button class="button" hx-get="{{ request.path}}?action=cancel">{% trans "Cancel" %}</button>
</div>

<script>
(function(){
const initEditor = (id) => {
const converterOne = window.Markdown.getSanitizingConverter();
const commentEditor = new window.Markdown.Editor(converterOne,id);
commentEditor.run();
};
initEditor("-edit-comment-{{ activity.id }}");
})();
</script>

</form>

30 changes: 24 additions & 6 deletions hypha/apply/activity/templates/activity/include/comment_list.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
{% load i18n %}

{% for comment in comments %}
{% include "activity/include/listing_base.html" with activity=comment %}
{% endfor %}
<div class="border-b-2 border-b-slate-300">
<div class="max-w-3xl ml-4 md:ml-20">
<div class="comment-list-content">
{% for comment in comments %}
{% include "activity/include/listing_base.html" with activity=comment %}
{% endfor %}

{% if not comments %}
{% trans "No comments available" %}
{% endif %}
{% if page.has_next %}
<a
href="{{ request.path }}?page={{ page.next_page_number }}"
class="block text-sm text-slate-500 hover:text-slate-700 py-2"
hx-get="{{ request.path }}?page={{ page.next_page_number }}"
hx-trigger="revealed"
hx-target="this"
hx-swap="outerHTML"
hx-select=".comment-list-content"
>Show more...</a>
{% endif %}
</div>

{% if not comments %}
{% trans "No comments available" %}
{% endif %}
</div>
</div>
180 changes: 91 additions & 89 deletions hypha/apply/activity/templates/activity/include/listing_base.html
Original file line number Diff line number Diff line change
@@ -1,101 +1,103 @@
{% load i18n activity_tags nh3_tags markdown_tags submission_tags apply_tags heroicons %}
{% load i18n activity_tags nh3_tags markdown_tags submission_tags apply_tags heroicons users_tags %}

<div class="feed__item feed__item--{{ activity.type }} border shadow-sm rounded-sm pb-2 " id="communications#{{ activity.id }}">
<div class="feed__pre-content hidden lg:block">
<p class="feed__label lg:py-2 feed__label--{{ activity.type }}">
{% if activity.type == 'comment' %}
{% heroicon_mini "chat-bubble-left" class="inline align-text-bottom me-2" aria_hidden=true %}
{% else %}
{{ activity.type|capfirst }}
{% endif %}
</p>
</div>
<div class="feed__content js-feed-content">
<div class="feed__meta js-feed-meta py-2 bg-slate-50 shadow-sm">
<p class="feed__meta-item ps-3">
<span>{{ activity|display_activity_author:request.user }}</span>
<relative-time class="text-fg-muted text-sm" data-tippy-content="{{ activity.timestamp|date:"SHORT_DATETIME_FORMAT" }}" datetime={{ activity.timestamp|date:"c" }}>{{ activity.timestamp|date:"SHORT_DATETIME_FORMAT" }}</relative-time>
{% if activity.edited %}
<span class="js-last-edited text-fg-muted text-sm" data-tippy-content="{{ activity.edited|date:"SHORT_DATETIME_FORMAT" }}">{% trans "edited" %}</span>
{% endif %}
</p>

{% if editable and activity.user == request.user %}
<p class="feed__meta-item feed__meta-item--edit-button">
<a class="link link--edit-submission is-active js-edit-comment" href="#">
{% heroicon_mini "pencil-square" size=18 class="inline me-1" aria_hidden=true %}
{% trans "Edit" %}
</a>
</p>
{% endif %}

<p class="feed__meta-item feed__meta-item--right flex items-center">
{% heroicon_micro "eye" class="fill-fg-muted inline me-1 w-4 h-4 mr-1" aria_hidden=true %}
<span class="js-comment-visibility">{{ activity.visibility|visibility_display:request.user }}</span>
</p>
</div>
<div class="timeline-item relative" id="communications#{{ activity.id }}">
<div
class="flex {% if activity.type == 'comment' %}py-3 -ms-8 md:-ms-20{% else %}py-2 -ml-3 items-center {% endif %} before:block before:-z-10 before:absolute before:top-0 before:bottom-0 before:left-0 before:w-0.5 before:bg-slate-300"
>
{% with activity|display_author:request.user as author_name %}

<div class="feed__heading">
{% if submission_title %}
{% trans "updated" %} <a href="{{ activity.source.get_absolute_url }}">{{ activity.source.title }}</a>
{% endif %}

{% if editable %}
<div class="feed__comment js-comment px-3 prose"
data-id="{{activity.id}}" data-comment="{{activity|display_for:request.user|to_markdown}}"
data-visibility-options="{{activity|visibility_options:activity.user}}"
data-visibility="{{activity.visibility}}"
data-edit-url="{% url 'api:v1:comments-edit' pk=activity.pk %}"
>
{{ activity|display_for:request.user|submission_links|markdown|nh3 }}
</div>
<style>
@media only screen and (min-width: 1024px){
.js-edit-block .form .wmd-preview, .js-edit-block .form .wmd-input {
max-width: 70%;
}
}
</style>
<div class="js-edit-block pe-3" aria-live="polite"></div>
{% else %}
<div class="px-3 prose">
{{ activity|display_for:request.user|submission_links|markdown|nh3 }}
<div class="relative inline-flex justify-center {% if activity.type == 'comment' %}me-4 items-start{% else %}me-2 items-center {% endif %}">
<div class="relative rounded-full inline-flex items-center justify-center border-white border-2 -ms-0.5 {% if activity.user.is_staff %}bg-slate-200 {% else %}bg-gray-100{% endif %}">
{% if activity.type == 'comment' %}
<div class="md:block hidden p-2">
{% user_image author_name size=25 %}
</div>
{% else %}
<div class="inline-flex items-center justify-center w-6 h-6">
{% heroicon_micro "eye" class="fill-gray-400 inline" aria_hidden=true %}
</div>
{% endif %}
</div>
{% endif %}
</div>


{% if not submission_title and activity|user_can_see_related:request.user %}
{% with url=activity.related_object.get_absolute_url %}
{% if url %}
<a href="{{ url }}" class="p-1 ms-2 mt-2 text-sm inline-flex items-center hover:opacity-70 transition-opacity font-bold">
<span class="uppercase">{% trans "View " %}{{ activity.related_object|model_verbose_name }}</span>
{% heroicon_micro "chevron-double-right" class="inline w-4 h-4 ms-1" aria_hidden=true %}
</a>
{% endif %}
{% endwith %}
{% endif %}
{% if activity.type == 'comment' %}
<div class="border-slate-200 flex-1 pb-2 bg-white border rounded shadow-sm">
<div class="bg-slate-100 flex flex-wrap items-center justify-between gap-2 p-2 rounded-t">
<div>
<span>
<strong class="font-semibold">{{ author_name }}</strong>

</div>
{% with activity.attachments.all as attachments %}
{% if attachments %}
<div class="section-attachments flex gap-2 flex-col max-w-xl mt-4 px-3 pb-2">
{% for attachment in attachments %}
<a href="{{attachment.get_absolute_url }}"
class="flex justify-between border rounded px-3 py-2 font-medium bg-slate-50 hover:bg-slate-200 transition-colors"
target="_blank"
rel="noopener noreferrer"
title="{{ attachment.filename }}"
>
<span class="truncate text-sm">
{% heroicon_mini "paper-clip" class="inline align-text-bottom" aria_hidden=true %}
{{ attachment.filename|truncatechars_middle:45 }}
{% if activity.type == 'comment' %}
<span class="text-fg-muted">commented</span>
{% endif %}

<relative-time
class="text-fg-muted"
data-tippy-content="{{ activity.timestamp|date:"SHORT_DATETIME_FORMAT" }}"
datetime={{ activity.timestamp|date:"c" }}
>{{ activity.timestamp|date:"SHORT_DATETIME_FORMAT" }}</relative-time>
</span>

<span>
{% heroicon_mini "arrow-small-down" class="inline align-text-bottom rounded" aria_hidden=true %}
{% for role in activity.user.get_role_names %}
<span
class="text-fg-muted font-semibold inline-block px-2 py-0.5 text-sm border border-gray-300 rounded-xl"
data-tippy-content="This user is a {{ role }}"
>
{{ role }}
</span>
{% endfor %}
</span>
</a>
{% endfor %}
</div>
<div class="align-middle">
{% if not request.user.is_applicant %}
<span class="text-fg-muted text-xs">({{ activity.visibility|visibility_display:request.user }})</span>
{% endif %}

{% if editable and activity.user == request.user %}
<a
hx-get="{% url 'activity:edit-comment' activity.id %}"
hx-target="#text-comment-{{activity.id}}"
title="Edit Comment"
class="inline-flex items-center"
>
{% heroicon_micro "pencil-square" size=18 class="me-1 inline" aria_hidden=true %}
<span class="sr-only">{% trans "Edit" %}</span>
</a>
{% endif %}
</div>
</div>

<div class="">
{% if submission_title %}
{% trans "updated" %} <a href="{{ activity.source.get_absolute_url }}">{{ activity.source.title }}</a>
{% endif %}

<div class="px-3 pt-2" id="text-comment-{{activity.id}}">
{% include 'activity/partial_comment_message.html' with activity=activity %}
</div>

{% if not submission_title and activity|user_can_see_related:request.user %}
<div class="px-3">
{% with url=activity.related_object.get_absolute_url %}
{% if url %}
<a href="{{ url }}">
{% trans "View " %}{{ activity.related_object|model_verbose_name }} <svg class="icon inline"><use xlink:href="#arrow-head-pixels--solid"></use></svg>
</a>
{% endif %}
{% endwith %}
</div>
{% endif %}
</div>
</div>
{% else %}
<div class="text-sm">
<span class="font-medium">{{ activity|display_author:request.user }}</span>
<span class="text-fg-muted">
{{ activity|display_for:request.user|striptags|lowerfirst }}
<relative-time datetime={{ activity.timestamp|date:"c" }}>{{ activity.timestamp|date:"SHORT_DATETIME_FORMAT" }}</relative-time>
</span>
</div>
{% endif %}
{% endwith %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{% load heroicons activity_tags nh3_tags markdown_tags submission_tags apply_tags users_tags %}

<div class="prose max-w-none">
{{ activity|display_for:request.user|submission_links|markdown|nh3 }}
</div>

{% if activity.edited %}
<span class="text-fg-muted text-sm" data-tippy-content="{{ activity.edited|date:"SHORT_DATETIME_FORMAT" }}">(edited)</span>
{% endif %}

{% with activity.attachments.all as attachments %}
{% if attachments %}
<div class="section-attachments flex gap-2 flex-col max-w-xl mt-4 pb-2">
{% for attachment in attachments %}
<a href="{{attachment.get_absolute_url }}"
class="flex justify-between border rounded px-3 py-2 font-medium bg-slate-50 hover:bg-slate-200 transition-colors"
target="_blank"
rel="noopener noreferrer"
title="{{ attachment.filename }}"
>
<span class="truncate text-sm">
{% heroicon_mini "paper-clip" class="inline align-text-bottom" aria_hidden=true %}
{{ attachment.filename|truncatechars_middle:45 }}
</span>
<span>
{% heroicon_mini "arrow-small-down" class="inline align-text-bottom rounded" aria_hidden=true %}
</span>
</a>
{% endfor %}
</div>
{% endif %}
{% endwith %}
13 changes: 12 additions & 1 deletion hypha/apply/activity/templatetags/activity_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django import template
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.template.defaultfilters import stringfilter

from hypha.apply.determinations.models import Determination
from hypha.apply.funds.models.submissions import ApplicationSubmission
Expand Down Expand Up @@ -36,16 +37,19 @@ def display_activity_author(activity, user) -> str:
and activity.user.is_org_faculty
):
return settings.ORG_LONG_NAME

if isinstance(activity.related_object, Review) and activity.source.user == user:
return _("Reviewer")

if (
settings.HIDE_IDENTITY_FROM_REVIEWERS
and isinstance(activity.source, ApplicationSubmission)
and activity.user == activity.source.user
and user in activity.source.reviewers.all()
):
return _("Applicant")
return activity.user.get_display_name_with_group()

return activity.user.get_display_name()


@register.filter
Expand Down Expand Up @@ -165,3 +169,10 @@ def source_type(value) -> str:
if value and "submission" in value:
return "Submission"
return str(value).capitalize()


@register.filter(is_safe=True)
@stringfilter
def lowerfirst(value):
"""Lowercase the first character of the value."""
return value and value[0].lower() + value[1:]
Loading

0 comments on commit eec6ead

Please sign in to comment.