feat: Enable mentions and notify users from any text field
This commit is contained in:
parent
b52fdbba45
commit
53c22b0493
7 changed files with 92 additions and 94 deletions
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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') : ''}`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue