feat: Enable mentions and notify users from any text field

This commit is contained in:
Nabin Hait 2022-07-22 15:26:30 +05:30
parent b52fdbba45
commit 53c22b0493
7 changed files with 92 additions and 94 deletions

View file

@ -3,23 +3,16 @@
import json
import frappe
from frappe import _
from frappe.core.doctype.user.user import extract_mentions
from frappe.database.schema import add_column
from frappe.desk.doctype.notification_log.notification_log import (
enqueue_create_notification,
get_title,
get_title_html,
)
from frappe.desk.notifications import notify_mentions
from frappe.exceptions import ImplicitCommitError
from frappe.model.document import Document
from frappe.utils import get_fullname
from frappe.website.utils import clear_cache
class Comment(Document):
def after_insert(self):
self.notify_mentions()
notify_mentions(self.reference_doctype, self.reference_name, self.content)
self.notify_change("add")
def validate(self):
@ -63,40 +56,6 @@ class Comment(Document):
update_comments_in_parent(self.reference_doctype, self.reference_name, _comments)
def notify_mentions(self):
if self.reference_doctype and self.reference_name and self.content:
mentions = extract_mentions(self.content)
if not mentions:
return
sender_fullname = get_fullname(frappe.session.user)
title = get_title(self.reference_doctype, self.reference_name)
recipients = [
frappe.db.get_value(
"User",
{"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1},
"email",
)
for name in mentions
]
notification_message = _("""{0} mentioned you in a comment in {1} {2}""").format(
frappe.bold(sender_fullname), frappe.bold(self.reference_doctype), get_title_html(title)
)
notification_doc = {
"type": "Mention",
"document_type": self.reference_doctype,
"document_name": self.reference_name,
"subject": notification_message,
"from_user": frappe.session.user,
"email_content": self.content,
}
enqueue_create_notification(recipients, notification_doc)
def on_doctype_update():
frappe.db.add_index("Comment", ["reference_doctype", "reference_name"])

View file

@ -8,13 +8,13 @@ from unittest.mock import patch
import frappe
import frappe.exceptions
from frappe.core.doctype.user.user import (
extract_mentions,
reset_password,
sign_up,
test_password_strength,
update_password,
verify_password,
)
from frappe.desk.notifications import extract_mentions
from frappe.frappeclient import FrappeClient
from frappe.model.delete_doc import delete_doc
from frappe.utils import get_url

View file

@ -2,8 +2,6 @@
# License: MIT. See LICENSE
from datetime import timedelta
from bs4 import BeautifulSoup
import frappe
import frappe.defaults
import frappe.permissions
@ -1044,24 +1042,6 @@ def notify_admin_access_to_system_manager(login_manager=None):
)
def extract_mentions(txt):
"""Find all instances of @mentions in the html."""
soup = BeautifulSoup(txt, "html.parser")
emails = []
for mention in soup.find_all(class_="mention"):
if mention.get("data-is-group") == "true":
try:
user_group = frappe.get_cached_doc("User Group", mention["data-id"])
emails += [d.user for d in user_group.user_group_members]
except frappe.DoesNotExistError:
pass
continue
email = mention["data-id"]
emails.append(email)
return emails
def handle_password_test_fail(result):
suggestions = result["feedback"]["suggestions"][0] if result["feedback"]["suggestions"] else ""
warning = result["feedback"]["warning"] if "warning" in result["feedback"] else ""

View file

@ -40,7 +40,6 @@ class Workspace:
self.allowed_modules = self.get_cached("user_allowed_modules", self.get_allowed_modules)
self.doc = frappe.get_cached_doc("Workspace", self.page_name)
if (
self.doc
and self.doc.module

View file

@ -3,10 +3,19 @@
import json
from bs4 import BeautifulSoup
import frappe
from frappe import _
from frappe.desk.doctype.notification_log.notification_log import (
enqueue_create_notification,
get_title,
get_title_html,
)
from frappe.desk.doctype.notification_settings.notification_settings import (
get_subscribed_documents,
)
from frappe.utils import get_fullname
@frappe.whitelist()
@ -298,3 +307,56 @@ def get_open_count(doctype, name, items=None):
out["timeline_data"] = module.get_timeline_data(doctype, name)
return out
def notify_mentions(ref_doctype, ref_name, content):
if ref_doctype and ref_name and content:
mentions = extract_mentions(content)
if not mentions:
return
sender_fullname = get_fullname(frappe.session.user)
title = get_title(ref_doctype, ref_name)
recipients = [
frappe.db.get_value(
"User",
{"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1},
"email",
)
for name in mentions
]
notification_message = _("""{0} mentioned you in a comment in {1} {2}""").format(
frappe.bold(sender_fullname), frappe.bold(ref_doctype), get_title_html(title)
)
notification_doc = {
"type": "Mention",
"document_type": ref_doctype,
"document_name": ref_name,
"subject": notification_message,
"from_user": frappe.session.user,
"email_content": content,
}
enqueue_create_notification(recipients, notification_doc)
def extract_mentions(txt):
"""Find all instances of @mentions in the html."""
soup = BeautifulSoup(txt, "html.parser")
emails = []
for mention in soup.find_all(class_="mention"):
if mention.get("data-is-group") == "true":
try:
user_group = frappe.get_cached_doc("User Group", mention["data-id"])
emails += [d.user for d in user_group.user_group_members]
except frappe.DoesNotExistError:
pass
continue
email = mention["data-id"]
emails.append(email)
return emails

View file

@ -71,36 +71,10 @@ frappe.ui.form.ControlComment = class ControlComment extends frappe.ui.form.Cont
const options = super.get_quill_options();
return Object.assign(options, {
theme: 'bubble',
bounds: this.quill_container[0],
modules: Object.assign(options.modules, {
mention: this.get_mention_options()
})
bounds: this.quill_container[0]
});
}
get_mention_options() {
if (!this.enable_mentions) {
return null;
}
let me = this;
return {
allowedChars: /^[A-Za-z0-9_]*$/,
mentionDenotationChars: ["@"],
isolateCharacter: true,
source: frappe.utils.debounce(async function(search_term, renderList) {
let method = me.mention_search_method || 'frappe.desk.search.get_names_for_mentions';
let values = await frappe.xcall(method, {
search_term
});
renderList(values, search_term);
}, 300),
renderItem(item) {
let value = item.value;
return `${value} ${item.is_group ? frappe.utils.icon('users') : ''}`;
}
};
}
get_toolbar_options() {
return [
['bold', 'italic', 'underline', 'strike'],

View file

@ -174,9 +174,33 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
toolbar: this.get_toolbar_options(),
table: true,
imageResize: {},
magicUrl: true
magicUrl: true,
mention: this.get_mention_options()
},
theme: 'snow'
theme: 'snow',
};
}
get_mention_options() {
if (!this.enable_mentions && !this.df.enable_mentions) {
return null;
}
let me = this;
return {
allowedChars: /^[A-Za-z0-9_]*$/,
mentionDenotationChars: ["@"],
isolateCharacter: true,
source: frappe.utils.debounce(async function(search_term, renderList) {
let method = me.mention_search_method || 'frappe.desk.search.get_names_for_mentions';
let values = await frappe.xcall(method, {
search_term
});
renderList(values, search_term);
}, 300),
renderItem(item) {
let value = item.value;
return `${value} ${item.is_group ? frappe.utils.icon('users') : ''}`;
}
};
}