diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index fcd32c52c9..3d8dbc1ced 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -8,7 +8,8 @@ from frappe import _ import json from frappe.model.document import Document from frappe.core.doctype.user.user import extract_mentions -from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification +from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\ + get_title, get_title_html from frappe.utils import get_fullname from frappe.website.render import clear_cache from frappe.database.schema import add_column @@ -51,15 +52,13 @@ class Comment(Document): return sender_fullname = get_fullname(frappe.session.user) - title_field = frappe.get_meta(self.reference_doctype).get_title_field() - title = self.reference_name if title_field == "name" else \ - frappe.db.get_value(self.reference_doctype, self.reference_name, title_field) + 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), frappe.bold(title)) + .format(frappe.bold(sender_fullname), frappe.bold(self.reference_doctype), get_title_html(title)) notification_doc = { 'type': 'Mention', diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 8f43cdc2b9..c187a8a392 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -50,13 +50,17 @@ def make_notification_logs(doc, users): _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') - _doc.insert(ignore_permissions=True) + if _doc.for_user != _doc.from_user or doc.type == 'Energy Point': + _doc.insert(ignore_permissions=True) def send_notification_email(doc): is_type_enabled = is_email_notifications_enabled_for_type(doc.for_user, doc.type) if not is_type_enabled: return + if doc.type == 'Energy Point' and doc.email_content is None: + return + from frappe.utils import get_url_to_form, strip_html doc_link = get_url_to_form(doc.document_type, doc.document_name) @@ -88,10 +92,22 @@ def get_email_header(doc): }[doc.type or 'Default'] +def get_title(doctype, docname, title_field=None): + if not title_field: + title_field = frappe.get_meta(doctype).get_title_field() + title = docname if title_field == "name" else \ + frappe.db.get_value(doctype, docname, title_field) + return title + +def get_title_html(title): + return '{0}'.format(title) + @frappe.whitelist() -def mark_as_seen(docnames): - docnames = frappe.parse_json(docnames) - if docnames: - filters = {'name': ['in', docnames]} - frappe.db.set_value('Notification Log', filters, 'seen', 1, update_modified=False) - frappe.publish_realtime('seen_notification', after_commit=True, user=frappe.session.user) +def mark_as_seen(docname): + if docname: + frappe.db.set_value('Notification Log', docname, 'seen', 1, update_modified=False) + + +@frappe.whitelist() +def trigger_indicator_hide(): + frappe.publish_realtime('indicator_hide', user=frappe.session.user) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 5841f9474a..9714f31d1f 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -7,7 +7,8 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.desk.form.document_follow import follow_document -from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification +from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\ + get_title, get_title_html import frappe.utils import frappe.share @@ -160,17 +161,15 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', # Search for email address in description -- i.e. assignee user_name = frappe.get_cached_value('User', frappe.session.user, 'full_name') - title_field = frappe.get_meta(doc_type).get_title_field() - title = doc_name if title_field == "name" else \ - frappe.db.get_value(doc_type, doc_name, title_field) + title = get_title(doc_type, doc_name) description_html = "
{0}
".format(description) if description else None if action=='CLOSE': - subject = _('Your assignment on {0} {1} has been removed').format(frappe.bold(doc_type), frappe.bold(title)) + subject = _('Your assignment on {0} {1} has been removed').format(frappe.bold(doc_type), get_title_html(title)) else: user_name = frappe.bold(user_name) document_type = frappe.bold(doc_type) - title = frappe.bold(title) + title = get_title_html(title) subject = _('{0} assigned a new task {1} {2} to you').format(user_name, document_type, title) notification_doc = { diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index e0915e927c..8c6697e350 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -24,8 +24,8 @@ frappe.ui.Notifications = class Notifications { this.$open_docs = this.$dropdown_list.find( '.category-list[data-category="Open Documents"]' ); - this.$upcoming_events = this.$dropdown_list.find( - '.category-list[data-category="Upcoming Events"]' + this.$today_events = this.$dropdown_list.find( + '.category-list[data-category="Todays Events"]' ); frappe.utils.bind_actions_with_object(this.$dropdown_list, this); @@ -44,18 +44,13 @@ frappe.ui.Notifications = class Notifications { }); } - render_upcoming_events(e, $target) { + render_todays_events(e, $target) { let hide = $target.next().hasClass('in'); if (!hide) { let today = frappe.datetime.get_today(); - let tomorrow = frappe.datetime.add_days(today, 1); - frappe.db.get_list('Event', { - fields: ['name', 'subject', 'starts_on'], - filters: [ - {'starts_on': ['between', today, tomorrow]}, - {'ends_on': ['>=', frappe.datetime.now_datetime()]}, - {'owner': frappe.session.user} - ] + frappe.xcall('frappe.desk.doctype.event.event.get_events', { + start: today, + end: today }).then(event_list => { this.render_events_html(event_list); }); @@ -75,11 +70,11 @@ frappe.ui.Notifications = class Notifications { html = event_list.map(get_event_html).join(''); } else { html = `
  • - ${__('No Upcoming Events')} + ${__('No Events Today')}
  • `; } - this.$upcoming_events.html(html); + this.$today_events.html(html); } get_open_document_config(e) { @@ -222,23 +217,34 @@ frappe.ui.Notifications = class Notifications { change_activity_status() { if (this.$dropdown_list.find('.activity-status')) { this.$dropdown_list.find('.activity-status').replaceWith( - ` - ${__('View Full Log')} +
    ${__('View Full Log')}
    ` ); } } - mark_as_seen() { - let unseen_docnames = this.dropdown_items - .filter(item => item.seen === 0) - .map(d => d.name); - if (!unseen_docnames.length) return; + set_field_as_seen(docname, $el) { frappe.call( 'frappe.desk.doctype.notification_log.notification_log.mark_as_seen', - { docnames: unseen_docnames } - ); + { docname: docname } + ).then(()=> { + $el.removeClass('unseen'); + }); + } + + explicitly_mark_as_seen(e, $target) { + e.preventDefault(); + e.stopImmediatePropagation(); + let docname = $target.parents('.unseen').attr('data-name'); + this.set_field_as_seen(docname, $target.parents('.unseen')); + } + + mark_as_seen(e, $target) { + let docname = $target.attr('data-name'); + let df = this.dropdown_items.filter(f => docname.includes(f.name))[0]; + this.set_field_as_seen(df.name, $target); } get_notifications_list(limit) { @@ -265,9 +271,9 @@ frappe.ui.Notifications = class Notifications { let item_html = this.get_dropdown_item_html(field); if (item_html) body_html += item_html; }); - view_full_log_html = ` - ${__('View Full Log')} +
    ${__('View Full Log')}
    `; } else { body_html += `
  • @@ -287,18 +293,29 @@ frappe.ui.Notifications = class Notifications { field.document_name ); let seen_class = field.seen ? '' : 'unseen'; + let mark_seen_action = field.seen ? '': 'data-action="mark_as_seen"'; let message = field.subject; + let title = message.match(/(.*?)<\/b>/); + message = title ? message.replace(title[1], frappe.ellipsis(title[1], 100)): message; let message_html = `
    ${message}
    `; let user = field.from_user; let user_avatar = frappe.avatar(user, 'avatar-small user-avatar'); let timestamp = frappe.datetime.comment_when(field.creation, true); - let item_html = ` + let item_html = + ` ${user_avatar} ${message_html}
    ${timestamp}
    -
    `; + + ${__('Mark as Read')} + + `; return item_html; } @@ -306,18 +323,18 @@ frappe.ui.Notifications = class Notifications { render_dropdown_headers() { this.categories = [ { - label: __('Notifications'), - value: 'Notifications' + label: __("Notifications"), + value: "Notifications" }, { - label: __('Upcoming Events'), - value: 'Upcoming Events', - action: 'render_upcoming_events' + label: __("Today's Events"), + value: "Todays Events", + action: "render_todays_events" }, { - label: __('Open Documents'), - value: 'Open Documents', - action: 'get_open_document_config' + label: __("Open Documents"), + value: "Open Documents", + action: "get_open_document_config" } ]; @@ -377,8 +394,8 @@ frappe.ui.Notifications = class Notifications { } bind_events() { - this.setup_notification_listeners(); this.setup_dropdown_events(); + this.setup_notification_listeners(); this.$dropdown_list.on('click', '.recent-item', () => { this.$dropdown.removeClass('open'); @@ -399,7 +416,7 @@ frappe.ui.Notifications = class Notifications { this.update_dropdown(); }); - frappe.realtime.on('seen_notification', () => { + frappe.realtime.on('indicator_hide', () => { this.$dropdown.find('.notifications-indicator').hide(); }); } @@ -407,7 +424,7 @@ frappe.ui.Notifications = class Notifications { setup_dropdown_events() { this.$dropdown_list .find( - '[data-category="Notifications"], [data-category="Upcoming Events"], [data-category="Open Documents"]' + '[data-category="Notifications"], [data-category="Todays Events"], [data-category="Open Documents"]' ) .collapse({ toggle: false @@ -420,17 +437,20 @@ frappe.ui.Notifications = class Notifications { .collapse('show'); this.$dropdown_list .find( - '[data-category="Upcoming Events"], [data-category="Open Documents"]' + '[data-category="Todays Events"], [data-category="Open Documents"]' ) .collapse('hide'); } - this.$dropdown_list.find('.unseen').removeClass('unseen'); $(e.currentTarget).data('closable', true); return hide; }); this.$dropdown.on('show.bs.dropdown', () => { - this.mark_as_seen(); + if (this.$notification_indicator.is(':visible')) { + frappe.call( + 'frappe.desk.doctype.notification_log.notification_log.trigger_indicator_hide' + ); + } }); this.$dropdown.on('click', e => { diff --git a/frappe/public/less/notifications.less b/frappe/public/less/notifications.less index adc15ec3ae..9c40a90a71 100644 --- a/frappe/public/less/notifications.less +++ b/frappe/public/less/notifications.less @@ -52,14 +52,30 @@ display: flow-root; } +.category-list[data-category="Notifications"] .recent-item { + padding: 15px 14px 0 14px; +} + +.full-log-btn { + padding-bottom: 10px; +} + a.recent-item:hover { background-color: #f0f4f7; } -.dropdown-energy-points .energy-points-icon { - height: 40px; - font-size: 14px; - text-align: center; +a.unseen:hover .mark-read { + display: inline-block; +} + +.mark-read { + display: none; + margin-left: 10px; + font-size: 11px; +} + +.mark-read:hover { + text-decoration: underline; } .notifications-list { @@ -68,15 +84,6 @@ a.recent-item:hover { overflow-y: auto; } -.energy-points-notification { - font-size: 7px; - position: absolute; - top: 4px; - right: 8px; - color: @indicator-orange; - display: none; -} - .date-range { padding: 10px 0 2px 10px; font-weight: 500; @@ -89,6 +96,8 @@ a.recent-item:hover { .notification-timestamp { margin-top: 5px; font-size: 11px; + display: inline-block; + margin-bottom: 10px; } .user-avatar { diff --git a/frappe/share.py b/frappe/share.py index c8471bfe2c..3875870949 100644 --- a/frappe/share.py +++ b/frappe/share.py @@ -5,7 +5,8 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.desk.form.document_follow import follow_document -from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification +from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\ + get_title, get_title_html from frappe.utils import cint @frappe.whitelist() @@ -152,13 +153,11 @@ def notify_assignment(shared_by, doctype, doc_name, everyone): from frappe.utils import get_fullname - title_field = frappe.get_meta(doctype).get_title_field() - title = doc_name if title_field == "name" else \ - frappe.db.get_value(doctype, doc_name, title_field) + title = get_title(doctype, doc_name) reference_user = get_fullname(frappe.session.user) notification_message = _('{0} shared a document {1} {2} with you').format( - frappe.bold(reference_user), frappe.bold(doctype), frappe.bold(title)) + frappe.bold(reference_user), frappe.bold(doctype), get_title_html(title)) notification_doc = { 'type': 'Share', diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py index c347c81b1e..724117df2e 100644 --- a/frappe/social/doctype/energy_point_log/energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/energy_point_log.py @@ -7,7 +7,8 @@ import frappe from frappe import _ import json from frappe.model.document import Document -from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification +from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\ + get_title, get_title_html from frappe.utils import cint, get_fullname, getdate, get_link_to_form class EnergyPointLog(Document): @@ -38,7 +39,7 @@ class EnergyPointLog(Document): 'document_name': self.reference_name, 'subject': get_notification_message(self), 'from_user': reference_user, - 'email_content': '
    {}
    '.format(self.reason) + 'email_content': '
    {}
    '.format(self.reason) if self.reason else None } enqueue_create_notification(self.user, notification_doc) @@ -46,9 +47,7 @@ class EnergyPointLog(Document): def get_notification_message(doc): owner_name = get_fullname(doc.owner) points = doc.points - title_field = frappe.get_meta(doc.reference_doctype).get_title_field() - title = doc.reference_name if title_field == "name" else \ - frappe.db.get_value(doc.reference_doctype, doc.reference_name, title_field) + title = get_title(doc.reference_doctype, doc.reference_name) if doc.type == 'Auto': owner_name = frappe.bold('You') @@ -56,26 +55,26 @@ def get_notification_message(doc): message = _('{0} gained {1} point for {2} {3}') else: message = _('{0} gained {1} points for {2} {3}') - message = message.format(owner_name, frappe.bold(points), doc.rule, frappe.bold(title)) + message = message.format(owner_name, frappe.bold(points), doc.rule, get_title_html(title)) elif doc.type == 'Appreciation': if points == 1: message = _('{0} appreciated your work on {1} with {2} point') else: message = _('{0} appreciated your work on {1} with {2} points') - message = message.format(frappe.bold(owner_name), frappe.bold(title), frappe.bold(points)) + message = message.format(frappe.bold(owner_name), get_title_html(title), frappe.bold(points)) elif doc.type == 'Criticism': if points == 1: message = _('{0} criticized your work on {1} with {2} point') else: message = _('{0} criticized your work on {1} with {2} points') - message = message.format(frappe.bold(owner_name), frappe.bold(title), frappe.bold(points)) + message = message.format(frappe.bold(owner_name), get_title_html(title), frappe.bold(points)) elif doc.type == 'Revert': if points == 1: message = _('{0} reverted your point on {1}') else: message = _('{0} reverted your points on {1}') - message = message.format(frappe.bold(owner_name), frappe.bold(title)) + message = message.format(frappe.bold(owner_name), get_title_html(title)) return message