@@ -119,7 +119,7 @@ def get_fullnames(): | |||
ret = frappe.db.sql("""select name, | |||
concat(ifnull(first_name, ''), | |||
if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')) as fullname, | |||
user_image as image, gender, email | |||
user_image as image, gender, email, username | |||
from tabUser where enabled=1 and user_type!="Website User" """, as_dict=1) | |||
d = {} | |||
@@ -7,7 +7,8 @@ from frappe import _ | |||
from frappe.website.render import clear_cache | |||
from frappe.model.document import Document | |||
from frappe.model.db_schema import add_column | |||
from frappe.utils import get_fullname | |||
from frappe.utils import get_fullname, get_link_to_form | |||
from frappe.core.doctype.user.user import extract_mentions | |||
exclude_from_linked_with = True | |||
@@ -38,6 +39,7 @@ class Comment(Document): | |||
"""Send realtime updates""" | |||
if not self.comment_doctype: | |||
return | |||
if self.comment_doctype == 'Message': | |||
if self.comment_docname == frappe.session.user: | |||
message = self.as_dict() | |||
@@ -50,6 +52,8 @@ class Comment(Document): | |||
frappe.publish_realtime('new_comment', self.as_dict(), doctype= self.comment_doctype, | |||
docname = self.comment_docname) | |||
self.notify_mentions() | |||
def validate(self): | |||
"""Raise exception for more than 50 comments.""" | |||
if frappe.db.sql("""select count(*) from tabComment where comment_doctype=%s | |||
@@ -144,6 +148,33 @@ class Comment(Document): | |||
self.update_comments_in_parent(_comments) | |||
def notify_mentions(self): | |||
if self.comment_doctype and self.comment_docname and self.comment and self.comment_type=="Comment": | |||
mentions = extract_mentions(self.comment) | |||
if not mentions: | |||
return | |||
sender_fullname = get_fullname(frappe.session.user) | |||
parent_doc_label = "{0} {1}".format(_(self.comment_doctype), self.comment_docname) | |||
subject = _("{0} mentioned you in a comment in {1}").format(sender_fullname, parent_doc_label) | |||
message = frappe.get_template("templates/emails/mentioned_in_comment.html").render({ | |||
"sender_fullname": sender_fullname, | |||
"comment": self, | |||
"link": get_link_to_form(self.comment_doctype, self.comment_docname, label=parent_doc_label) | |||
}) | |||
recipients = [frappe.db.get_value("User", {"enabled": 1, "username": username, "user_type": "System User"}) | |||
for username in mentions] | |||
frappe.sendmail( | |||
recipients=recipients, | |||
sender=frappe.session.user, | |||
subject=subject, | |||
message=message, | |||
bulk=True | |||
) | |||
def on_doctype_update(): | |||
"""Add index to `tabComment` `(comment_doctype, comment_name)`""" | |||
if not frappe.db.sql("""show index from `tabComment` | |||
@@ -296,12 +296,13 @@ def validate_fields(meta): | |||
if d.fieldtype not in ("Data", "Link", "Read Only"): | |||
frappe.throw(_("Fieldtype {0} for {1} cannot be unique").format(d.fieldtype, d.label)) | |||
has_non_unique_values = frappe.db.sql("""select `{fieldname}`, count(*) | |||
from `tab{doctype}` group by `{fieldname}` having count(*) > 1 limit 1""".format( | |||
doctype=d.parent, fieldname=d.fieldname)) | |||
if not d.get("__islocal"): | |||
has_non_unique_values = frappe.db.sql("""select `{fieldname}`, count(*) | |||
from `tab{doctype}` group by `{fieldname}` having count(*) > 1 limit 1""".format( | |||
doctype=d.parent, fieldname=d.fieldname)) | |||
if has_non_unique_values and has_non_unique_values[0][0]: | |||
frappe.throw(_("Field '{0}' cannot be set as Unique as it has non-unique values").format(d.label)) | |||
if has_non_unique_values and has_non_unique_values[0][0]: | |||
frappe.throw(_("Field '{0}' cannot be set as Unique as it has non-unique values").format(d.label)) | |||
if d.search_index and d.fieldtype in ("Text", "Long Text", "Small Text", "Code", "Text Editor"): | |||
frappe.throw(_("Fieldtype {0} for {1} cannot be indexed").format(d.fieldtype, d.label)) | |||
@@ -24,6 +24,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -49,6 +50,7 @@ | |||
"oldfieldtype": "Check", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -72,6 +74,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -97,6 +100,7 @@ | |||
"options": "Email", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
@@ -121,6 +125,7 @@ | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
@@ -145,6 +150,7 @@ | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -169,6 +175,7 @@ | |||
"oldfieldtype": "Data", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -176,6 +183,30 @@ | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "username", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Username", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 1 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
@@ -194,6 +225,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -216,6 +248,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -238,6 +271,7 @@ | |||
"oldfieldtype": "Column Break", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "50%", | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -264,6 +298,7 @@ | |||
"options": "Loading...", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -287,6 +322,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -310,6 +346,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -333,6 +370,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -358,6 +396,7 @@ | |||
"options": "\nMale\nFemale\nOther", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -382,6 +421,7 @@ | |||
"oldfieldtype": "Date", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -404,6 +444,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -425,6 +466,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -447,6 +489,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -470,6 +513,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -493,6 +537,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -515,6 +560,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -539,6 +585,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -561,6 +608,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 1, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -585,6 +633,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -609,6 +658,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -631,6 +681,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -655,6 +706,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -678,6 +730,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -702,6 +755,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -726,6 +780,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 1, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -748,6 +803,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -771,6 +827,7 @@ | |||
"options": "UserRole", | |||
"permlevel": 1, | |||
"print_hide": 1, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -796,6 +853,7 @@ | |||
"permlevel": 1, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -819,6 +877,7 @@ | |||
"permlevel": 1, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -843,6 +902,7 @@ | |||
"permlevel": 1, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -867,6 +927,7 @@ | |||
"oldfieldtype": "Column Break", | |||
"permlevel": 1, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "50%", | |||
"read_only": 1, | |||
"report_hide": 0, | |||
@@ -893,6 +954,7 @@ | |||
"options": "DefaultValue", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -917,6 +979,7 @@ | |||
"oldfieldtype": "Section Break", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -944,6 +1007,7 @@ | |||
"options": "System User\nWebsite User", | |||
"permlevel": 1, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
@@ -967,6 +1031,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 1, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -990,6 +1055,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 1, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1013,6 +1079,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 1, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1035,6 +1102,7 @@ | |||
"oldfieldtype": "Column Break", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"print_width": "50%", | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -1061,6 +1129,7 @@ | |||
"oldfieldtype": "Read Only", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1085,6 +1154,7 @@ | |||
"oldfieldtype": "Read Only", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1108,6 +1178,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1132,6 +1203,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1155,6 +1227,7 @@ | |||
"no_copy": 0, | |||
"permlevel": 1, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1177,6 +1250,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1199,6 +1273,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1221,6 +1296,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1243,6 +1319,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1265,6 +1342,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1287,6 +1365,7 @@ | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
@@ -1305,7 +1384,8 @@ | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 5, | |||
"modified": "2015-11-16 06:29:59.828065", | |||
"menu_index": 0, | |||
"modified": "2015-12-23 02:45:19.261689", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "User", | |||
@@ -10,6 +10,7 @@ from frappe.desk.notifications import clear_notifications | |||
from frappe.utils.user import get_system_managers | |||
import frappe.permissions | |||
import frappe.share | |||
import re | |||
STANDARD_USERS = ("Guest", "Administrator") | |||
@@ -38,6 +39,8 @@ class User(Document): | |||
self.update_gravatar() | |||
self.ensure_unique_roles() | |||
self.remove_all_roles_for_guest() | |||
self.validate_username() | |||
if self.language == "Loading...": | |||
self.language = None | |||
@@ -296,6 +299,52 @@ class User(Document): | |||
else: | |||
exists.append(d.role) | |||
def validate_username(self): | |||
if not self.username and self.is_new(): | |||
self.username = frappe.scrub(self.first_name) | |||
if not self.username: | |||
return | |||
# strip space and @ | |||
self.username = self.username.strip(" @") | |||
if self.username_exists(): | |||
frappe.msgprint(_("Username {0} already exists")) | |||
self.suggest_username() | |||
raise frappe.DuplicateEntryError(self.username) | |||
if not self.is_new(): | |||
old_username = self.get_db_value("username") | |||
if old_username and self.username != old_username and "System Manager" not in frappe.get_roles(): | |||
frappe.throw(_("Only a System Manager can change Username")) | |||
# should be made up of characters, numbers and underscore only | |||
if not re.match(r"^[\w]+$", self.username): | |||
frappe.throw(_("Username should not contain any special characters other than letters, numbers and underscore")) | |||
def suggest_username(self): | |||
def _check_suggestion(suggestion): | |||
if self.username != suggestion and not self.username_exists(suggestion): | |||
return suggestion | |||
return None | |||
# @firstname | |||
username = _check_suggestion(frappe.scrub(self.first_name)) | |||
if not username: | |||
# @firstname_last_name | |||
username = _check_suggestion(frappe.scrub("{0} {1}".format(self.first_name, self.last_name or ""))) | |||
if username: | |||
frappe.msgprint(_("Suggested Username: {0}").format(username)) | |||
return username | |||
def username_exists(self, username=None): | |||
return frappe.db.get_value("User", {"username": username or self.username, "name": ("!=", self.name)}) | |||
@frappe.whitelist() | |||
def get_languages(): | |||
from frappe.translate import get_lang_dict | |||
@@ -490,3 +539,7 @@ def notifify_admin_access_to_system_manager(login_manager=None): | |||
frappe.sendmail(recipients=get_system_managers(), subject=_("Administrator Logged In"), | |||
message=message, bulk=True) | |||
def extract_mentions(txt): | |||
"""Find all instances of @username in the string. | |||
The mentions will be separated by non-word characters or may appear at the start of the string""" | |||
return re.findall(r'(?:[^\w]|^)@([\w]*)', txt) |
@@ -109,3 +109,4 @@ frappe.patches.v6_9.rename_burmese_language | |||
frappe.patches.v6_11.rename_field_in_email_account | |||
execute:frappe.create_folder(os.path.join(frappe.local.site_path, 'private', 'files')) | |||
frappe.patches.v6_15.remove_property_setter_for_previous_field | |||
frappe.patches.v6_15.set_username |
@@ -0,0 +1,15 @@ | |||
import frappe | |||
def execute(): | |||
frappe.reload_doctype("User") | |||
# give preference to System Users | |||
users = frappe.db.sql_list("""select name from `tabUser` order by if(user_type='System User', 0, 1)""") | |||
for name in users: | |||
user = frappe.get_doc("User", name) | |||
if user.username: | |||
continue | |||
username = user.suggest_username() | |||
if username: | |||
user.db_set("username", username, update_modified=False) |
@@ -4,6 +4,7 @@ | |||
<div> | |||
<textarea style="height: 80px" style="margin-top: 10px;" | |||
class="form-control"></textarea> | |||
<input type="data" class="hidden mention-input"> | |||
</div> | |||
<div class="media"> | |||
<span class="pull-left avatar avatar-medium"> | |||
@@ -40,7 +40,10 @@ frappe.ui.form.Comments = Class.extend({ | |||
this.list.on("click", ".toggle-blockquote", function() { | |||
$(this).parent().siblings("blockquote").toggleClass("hidden"); | |||
}); | |||
this.setup_mentions(); | |||
}, | |||
refresh: function(scroll_to_end) { | |||
var me = this; | |||
@@ -66,6 +69,7 @@ frappe.ui.form.Comments = Class.extend({ | |||
this.frm.sidebar.refresh_comments(); | |||
}, | |||
render_comment: function(c) { | |||
var me = this; | |||
this.prepare_comment(c); | |||
@@ -310,5 +314,155 @@ frappe.ui.form.Comments = Class.extend({ | |||
}); | |||
return last_email; | |||
}, | |||
setup_mentions: function() { | |||
var me = this; | |||
this.cursor_from = this.cursor_to = 0 | |||
this.codes = $.ui.keyCode; | |||
this.up = $.Event("keydown", {"keyCode": this.codes.UP}); | |||
this.down = $.Event("keydown", {"keyCode": this.codes.DOWN}); | |||
this.enter = $.Event("keydown", {"keyCode": this.codes.ENTER}); | |||
this.setup_autocomplete_for_mentions(); | |||
this.setup_textarea_event(); | |||
}, | |||
setup_autocomplete_for_mentions: function() { | |||
var me = this; | |||
var username_user_map = {}; | |||
for (var name in frappe.boot.user_info) { | |||
var _user = frappe.boot.user_info[name]; | |||
username_user_map[_user.username] = _user; | |||
} | |||
this.mention_input = this.wrapper.find(".mention-input"); | |||
this.mention_input.autocomplete({ | |||
minLength: 0, | |||
autoFocus: true, | |||
source: Object.keys(username_user_map), | |||
select: function(event, ui) { | |||
var value = ui.item.value; | |||
var textarea_value = me.input.val(); | |||
var new_value = textarea_value.substring(0, me.cursor_from) | |||
+ value | |||
+ textarea_value.substring(me.cursor_to); | |||
me.input.val(new_value); | |||
var new_cursor_location = me.cursor_from + value.length; | |||
// move cursor to right position | |||
if (me.input[0].setSelectionRange) { | |||
me.input.focus(); | |||
me.input[0].setSelectionRange(new_cursor_location, new_cursor_location); | |||
} else if (me.input[0].createTextRange) { | |||
var range = input[0].createTextRange(); | |||
range.collapse(true); | |||
range.moveEnd('character', new_cursor_location); | |||
range.moveStart('character', new_cursor_location); | |||
range.select(); | |||
} else { | |||
me.input.focus(); | |||
} | |||
} | |||
}); | |||
this.mention_widget = this.mention_input.autocomplete("widget"); | |||
this.autocomplete_open = false; | |||
this.mention_input | |||
.on('autocompleteclose', function() { | |||
me.autocomplete_open = false; | |||
}) | |||
.on('autocompleteopen', function() { | |||
me.autocomplete_open = true; | |||
}); | |||
// dirty hack to prevent backspace from navigating back to history | |||
$(document).on("keydown", function(e) { | |||
if (e.which===me.codes.BACKSPACE && me.autocomplete_open && document.activeElement==me.mention_widget.get(0)) { | |||
// me.input.focus(); | |||
return false; | |||
} | |||
}); | |||
}, | |||
setup_textarea_event: function() { | |||
var me = this; | |||
// binding this in keyup to get the value after it is set in textarea | |||
this.input.keyup(function(e) { | |||
if (e.which===16) { | |||
// don't trigger for shift | |||
return; | |||
} else if ([me.codes.UP, me.codes.DOWN].indexOf(e.which)!==-1) { | |||
// focus on autocomplete if up and down arrows | |||
if (me.autocomplete_open) { | |||
me.mention_widget.focus(); | |||
me.mention_widget.trigger(e.which===me.codes.UP ? me.up : me.down); | |||
} | |||
return; | |||
} else if ([me.codes.ENTER, me.codes.ESCAPE, me.codes.TAB, me.codes.SPACE].indexOf(e.which)!==-1) { | |||
me.mention_input.autocomplete("close"); | |||
return; | |||
} else if (e.which !== 0 && !e.ctrlKey && !e.metaKey && !e.altKey) { | |||
if(!String.fromCharCode(e.which)) { | |||
// no point in parsing it if it is not a character key | |||
return; | |||
} | |||
} | |||
var value = $(this).val() || ""; | |||
var i = e.target.selectionStart; | |||
var key = value[i-1]; | |||
var substring = value.substring(0, i); | |||
var mention = substring.match(/(?=[^\w]|^)@([\w]*)$/); | |||
if (mention && mention.length) { | |||
var mention = mention[0].slice(1); | |||
// record location of cursor | |||
me.cursor_from = i - mention.length; | |||
me.cursor_to = i; | |||
// render autocomplete at the bottom of the textbox and search for mention | |||
me.mention_input.autocomplete("option", "position", { | |||
of: me.input, | |||
my: "left top", | |||
at: "left bottom" | |||
}); | |||
me.mention_input.autocomplete("search", mention); | |||
} else { | |||
me.cursor_from = me.cursor_to = 0; | |||
me.mention_input.autocomplete("close"); | |||
} | |||
}); | |||
// binding this in keydown to prevent default action | |||
this.input.keydown(function(e) { | |||
// enter, escape, tab | |||
if (me.autocomplete_open) { | |||
if ([me.codes.ENTER, me.codes.TAB].indexOf(e.which)!==-1) { | |||
// set focused value | |||
me.mention_widget.trigger(me.enter); | |||
// prevent default | |||
return false; | |||
} | |||
} | |||
}); | |||
} | |||
}); |
@@ -0,0 +1,7 @@ | |||
<p> | |||
{{ _("{0} mentioned you in a comment in {1}").format(sender_fullname, link) }} | |||
</p> | |||
<blockquote | |||
style="border-left: 3px solid #d1d8dd; padding: 7px 15px; margin-left: 0px;"> | |||
{{ comment.comment | markdown }} | |||
</blockquote> |