diff --git a/hypha/apply/activity/forms.py b/hypha/apply/activity/forms.py
index 6b5544a590..dcdf2a0239 100644
--- a/hypha/apply/activity/forms.py
+++ b/hypha/apply/activity/forms.py
@@ -1,12 +1,16 @@
+import re
+
from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from django_file_form.forms import FileFormMixin
+from hypha.apply.activity.utils import get_mentioned_email_regex
from hypha.apply.stream_forms.fields import MultiFileField
from hypha.apply.todo.options import COMMENT_TASK
from hypha.apply.todo.views import add_task_to_user
-from hypha.apply.users.models import STAFF_GROUP_NAME, User
+from hypha.apply.users.models import User
+from hypha.apply.users.roles import ROLES_ORG_FACULTY
from hypha.core.widgets import PagedownWidget
from .models import Activity, ActivityAttachment
@@ -14,19 +18,19 @@
class CommentForm(FileFormMixin, forms.ModelForm):
attachments = MultiFileField(label=_("Attachments"), required=False)
- assign_to = forms.ModelChoiceField(
- queryset=User.objects.filter(groups__name=STAFF_GROUP_NAME),
- required=False,
- empty_label=_("Select..."),
- label=_("Assign to"),
- )
+ # assign_to = forms.ModelChoiceField(
+ # queryset=User.objects.filter(groups__name=STAFF_GROUP_NAME),
+ # required=False,
+ # empty_label=_("Select..."),
+ # label=_("Assign to"),
+ # )
class Meta:
model = Activity
fields = (
"message",
"visibility",
- "assign_to",
+ # "assign_to",
)
labels = {
"visibility": "Visible to",
@@ -60,19 +64,37 @@ def __init__(self, *args, user=None, **kwargs):
visibility.choices = self.visibility_choices
visibility.initial = visibility.initial[0]
visibility.widget = forms.HiddenInput()
- if not user.is_apply_staff:
- self.fields["assign_to"].widget = forms.HiddenInput()
+ # if not user.is_apply_staff:
+ # self.fields["assign_to"].widget = forms.HiddenInput()
+
+ def clean_message(self):
+ staff_emails = (
+ User.objects.filter(groups__name__in=ROLES_ORG_FACULTY)
+ .distinct()
+ .values_list("email", flat=True)
+ )
+ emails_regex = get_mentioned_email_regex(staff_emails)
+ cleaned_emails = re.findall(emails_regex, self.cleaned_data["message"])
+ users = User.objects.filter(email__in=cleaned_emails)
+ self.cleaned_data["mentions"] = users
+ return self.cleaned_data["message"]
@transaction.atomic
def save(self, commit=True):
instance = super().save(commit=True)
added_files = self.cleaned_data["attachments"]
- assigned_user = self.cleaned_data["assign_to"]
- if assigned_user:
- # add task to assigned user
+ # assigned_user = self.cleaned_data["assign_to"]
+ # if assigned_user:
+ # # add task to assigned user
+ # add_task_to_user(
+ # code=COMMENT_TASK,
+ # user=assigned_user,
+ # related_obj=instance,
+ # )
+ for user in self.cleaned_data.get("mentions", []):
add_task_to_user(
code=COMMENT_TASK,
- user=assigned_user,
+ user=user,
related_obj=instance,
)
if added_files:
diff --git a/hypha/apply/activity/services.py b/hypha/apply/activity/services.py
index 30244596c7..4b26f96a11 100644
--- a/hypha/apply/activity/services.py
+++ b/hypha/apply/activity/services.py
@@ -1,10 +1,5 @@
-from django.contrib.contenttypes.models import ContentType
-from django.db.models import OuterRef, Subquery
-from django.db.models.functions import JSONObject
from django.utils import timezone
-from hypha.apply.todo.models import Task
-
from .models import Activity
@@ -83,21 +78,21 @@ def get_related_comments_for_user(obj, user):
.visible_to(user)
)
- if user.is_apply_staff:
- assigned_to_subquery = (
- Task.objects.filter(
- related_content_type=ContentType.objects.get_for_model(Activity),
- related_object_id=OuterRef("id"),
- )
- .select_related("user")
- .values(
- json=JSONObject(
- full_name="user__full_name", email="user__email", id="user__id"
- )
- )
- )
-
- queryset = queryset.annotate(assigned_to=Subquery(assigned_to_subquery))
+ # if user.is_apply_staff:
+ # assigned_to_subquery = (
+ # Task.objects.filter(
+ # related_content_type=ContentType.objects.get_for_model(Activity),
+ # related_object_id=OuterRef("id"),
+ # )
+ # .select_related("user")
+ # .values(
+ # json=JSONObject(
+ # full_name="user__full_name", email="user__email", id="user__id"
+ # )
+ # )
+ # )
+
+ # queryset = queryset.annotate(assigned_to=Subquery(assigned_to_subquery))
return queryset
diff --git a/hypha/apply/activity/templates/activity/include/comment_form.html b/hypha/apply/activity/templates/activity/include/comment_form.html
index fed4793699..38b358992c 100644
--- a/hypha/apply/activity/templates/activity/include/comment_form.html
+++ b/hypha/apply/activity/templates/activity/include/comment_form.html
@@ -1,4 +1,4 @@
-{% load i18n static %}
+{% load i18n static activity_tags %}
+
+ {% get_org_faculty_auto_suggest as faculty %}
+
+
diff --git a/hypha/apply/activity/templates/activity/include/notifications_dropdown.html b/hypha/apply/activity/templates/activity/include/notifications_dropdown.html
index 03003c18c7..4e5b34bb34 100644
--- a/hypha/apply/activity/templates/activity/include/notifications_dropdown.html
+++ b/hypha/apply/activity/templates/activity/include/notifications_dropdown.html
@@ -1,4 +1,4 @@
-{% load i18n activity_tags nh3_tags markdown_tags submission_tags apply_tags %}
+{% load i18n activity_tags nh3_tags markdown_tags apply_tags %}
{% for activity in object_list %}
diff --git a/hypha/apply/activity/templates/activity/partial_comment_message.html b/hypha/apply/activity/templates/activity/partial_comment_message.html
index dca46bdf01..9daf7def3d 100644
--- a/hypha/apply/activity/templates/activity/partial_comment_message.html
+++ b/hypha/apply/activity/templates/activity/partial_comment_message.html
@@ -1,7 +1,7 @@
-{% load heroicons activity_tags nh3_tags markdown_tags submission_tags apply_tags users_tags %}
+{% load heroicons activity_tags nh3_tags markdown_tags apply_tags users_tags %}
- {{ activity|display_for:request.user|submission_links|markdown|nh3 }}
+ {{ activity|display_for:request.user|submission_links:request.user|markdown|nh3 }}
{% if activity.edited %}
diff --git a/hypha/apply/activity/templates/activity/ui/activity-comment-item.html b/hypha/apply/activity/templates/activity/ui/activity-comment-item.html
index b56bb3578f..c7c34cd55e 100644
--- a/hypha/apply/activity/templates/activity/ui/activity-comment-item.html
+++ b/hypha/apply/activity/templates/activity/ui/activity-comment-item.html
@@ -1,4 +1,4 @@
-{% load i18n activity_tags nh3_tags markdown_tags submission_tags apply_tags heroicons users_tags %}
+{% load i18n activity_tags nh3_tags markdown_tags apply_tags heroicons users_tags %}
{% if not request.user.is_applicant %}
- {% if request.user.is_apply_staff and activity.assigned_to %}
+ {% comment %} {% if request.user.is_apply_staff and activity.assigned_to %}
{% heroicon_outline "user-plus" size=14 class="inline" aria_hidden=true %}
@@ -57,7 +57,7 @@
{% blocktrans with activity.assigned_to.full_name as assigned_to %}Assigned to {{ assigned_to }}{% endblocktrans %}
{% endif %}
- {% endif %}
+ {% endif %} {% endcomment %}
{% with activity.visibility|visibility_display:request.user as visibility_text %}
str:
def lowerfirst(value):
"""Lowercase the first character of the value."""
return value and value[0].lower() + value[1:]
+
+
+@register.simple_tag
+def get_org_faculty_auto_suggest() -> List[Dict]:
+ staff = User.objects.filter(groups__name__in=ROLES_ORG_FACULTY).distinct()
+ staff_list = [
+ {"email": user.email.lower(), "display": user.get_display_name()}
+ for user in staff
+ ]
+ return staff_list
+
+
+@register.filter
+def submission_links(value: str, user: User):
+ # regex to find #id in a string, which id can be alphanumeric, underscore, hyphen
+ submission_matches = re.findall(r"(?{submission.title} #{submission.public_id or submission.id}'
+ )
+
+ if links:
+ for sid, link in links.items():
+ value = re.sub(rf"(? str:
+ return rf"(?:^|(?<=\s))@({'|'.join(re.escape(email) for email in emails)})(?=\s|$)"
+
+
+def format_comment_mentions(value, user) -> str:
+ faculty = User.objects.filter(groups__name__in=ROLES_ORG_FACULTY).distinct()
+ email_regex = get_mentioned_email_regex(faculty.values_list("email", flat=True))
+ if faculty_matches := re.findall(email_regex, value):
+ qs = faculty.filter(email__in=faculty_matches)
+
+ for mention in qs:
+ extra_class = " bg-yellow-200" if mention == user else ""
+ user_display = f''
+ value = re.sub(
+ get_mentioned_email_regex([mention.email]), user_display, value
+ )
+
+ return value
diff --git a/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html b/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html
index ab86840c41..40bed80b69 100644
--- a/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html
+++ b/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html
@@ -1,4 +1,4 @@
-{% load i18n dashboard_statusbar_tags statusbar_tags workflow_tags heroicons submission_tags %}
+{% load i18n dashboard_statusbar_tags statusbar_tags workflow_tags heroicons %}
{% load can from permission_tags %}
{% for submission in page.object_list %}
diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html
index fb131d7730..9e8d8bcad2 100644
--- a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html
+++ b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html
@@ -1,5 +1,5 @@
{% extends "base-apply.html" %}
-{% load i18n static workflow_tags wagtailcore_tags statusbar_tags archive_tags submission_tags translate_tags %}
+{% load i18n static workflow_tags wagtailcore_tags statusbar_tags archive_tags translate_tags %}
{% load heroicons %}
{% load can from permission_tags %}
diff --git a/hypha/apply/funds/templates/funds/includes/screening_status_block.html b/hypha/apply/funds/templates/funds/includes/screening_status_block.html
index 84767ddc4e..46512ee368 100644
--- a/hypha/apply/funds/templates/funds/includes/screening_status_block.html
+++ b/hypha/apply/funds/templates/funds/includes/screening_status_block.html
@@ -1,4 +1,4 @@
-{% load i18n submission_tags heroicons %}
+{% load i18n heroicons %}
- {{ object.get_comments_display|submission_links }}
+ {{ object.get_comments_display|submission_links:request.user }}
- {{ object.output_answers|submission_links }}
+ {{ object.output_answers|submission_links:request.user }}
diff --git a/hypha/apply/todo/options.py b/hypha/apply/todo/options.py
index 7d3159dc36..24d534e8c1 100644
--- a/hypha/apply/todo/options.py
+++ b/hypha/apply/todo/options.py
@@ -51,7 +51,7 @@
# ADD Manual Task
COMMENT_TASK: {
"text": _(
- '{related.user} assigned you a comment on [{related.source.title}]({link} "{related.source.title}"):\n{msg}'
+ '{related.user} mentioned you in a comment on [{related.source.title}]({link} "{related.source.title}"):\n{msg}'
),
"icon": "comment",
"url": "{link}",
diff --git a/hypha/static_src/sass/components/_comment-suggestions.scss b/hypha/static_src/sass/components/_comment-suggestions.scss
new file mode 100644
index 0000000000..f5550b52ed
--- /dev/null
+++ b/hypha/static_src/sass/components/_comment-suggestions.scss
@@ -0,0 +1,46 @@
+.comment-mirror {
+ position: absolute;
+ top: 28px;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ color: transparent;
+ z-index: -1;
+}
+
+// .comment-suggestions {
+// display: none;
+// position: absolute;
+// width: fit-content;
+// z-index: 1000;
+// background-color: white;
+// border-radius: 0.25rem;
+// box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+// border-width: 1px;
+// cursor: pointer;
+// border-top-width: 1px;
+// border-bottom-width: 0px;
+
+// &__option {
+// padding-top: 0.25rem;
+// padding-bottom: 0.25rem;
+// padding-inline: 1rem;
+
+// &:not(:last-child) {
+// border-bottom: 1px solid rgb(229, 231, 235);
+// }
+
+// &:hover {
+// background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
+// }
+
+// &--display {
+// font-weight: bold;
+// }
+
+// &--email {
+// font-size: 0.9rem;
+// }
+// }
+// }
\ No newline at end of file
diff --git a/hypha/static_src/sass/main.scss b/hypha/static_src/sass/main.scss
index 893fc8f29b..ed0ef90844 100644
--- a/hypha/static_src/sass/main.scss
+++ b/hypha/static_src/sass/main.scss
@@ -49,6 +49,7 @@
@use "components/activity-notifications";
@use "components/dropdown";
@use "components/dashboard-table";
+@use "components/comment-suggestions";
// Layout
@use "layout/header";