From eda7b659a35b8786b4cec36acd66473e7551a93f Mon Sep 17 00:00:00 2001 From: mbauskar Date: Tue, 7 Mar 2017 06:40:31 +0530 Subject: [PATCH] [WIP] Email Inbox view for Communication --- frappe/boot.py | 4 +- .../communication/communication_list.js | 6 +- frappe/email/inbox.py | 33 +++++++ frappe/public/build.json | 4 + .../public/js/frappe/list/list_sidebar.html | 8 +- frappe/public/js/frappe/list/list_sidebar.js | 36 ++++++- frappe/public/js/frappe/list/list_view.js | 16 +++- frappe/public/js/frappe/ui/base_list.js | 4 +- .../public/js/frappe/views/communication.js | 7 +- .../js/frappe/views/inbox/inbox_view.js | 95 +++++++++++++++++++ .../inbox/inbox_view_item_main_head.html | 24 +++++ .../views/inbox/inbox_view_item_row.html | 34 +++++++ 12 files changed, 252 insertions(+), 19 deletions(-) create mode 100644 frappe/email/inbox.py create mode 100644 frappe/public/js/frappe/views/inbox/inbox_view.js create mode 100644 frappe/public/js/frappe/views/inbox/inbox_view_item_main_head.html create mode 100644 frappe/public/js/frappe/views/inbox/inbox_view_item_row.html diff --git a/frappe/boot.py b/frappe/boot.py index cbf8d7ac81..b05ad1b452 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -12,6 +12,7 @@ import frappe.desk.desk_page from frappe.desk.form.load import get_meta_bundle from frappe.utils.change_log import get_versions from frappe.translate import get_lang_dict +from frappe.email.inbox import get_email_accounts from frappe.core.doctype.feedback_trigger.feedback_trigger import get_enabled_feedback_trigger def get_bootinfo(): @@ -66,10 +67,9 @@ def get_bootinfo(): bootinfo.error_report_email = frappe.get_hooks("error_report_email") bootinfo.calendars = sorted(frappe.get_hooks("calendars")) bootinfo.treeviews = frappe.get_hooks("treeviews") or [] - bootinfo.email_accounts = frappe.get_all('User Email', fields=['email_account', 'email_id'], - filters=dict(parent=frappe.session.user)) bootinfo.lang_dict = get_lang_dict() bootinfo.feedback_triggers = get_enabled_feedback_trigger() + bootinfo.update(get_email_accounts(user=frappe.session.user)) return bootinfo diff --git a/frappe/core/doctype/communication/communication_list.js b/frappe/core/doctype/communication/communication_list.js index 458991f52f..53ae86a504 100644 --- a/frappe/core/doctype/communication/communication_list.js +++ b/frappe/core/doctype/communication/communication_list.js @@ -1,4 +1,8 @@ frappe.listview_settings['Communication'] = { - add_fields: ["sent_or_received", "recipients", "subject", "communication_medium", "communication_type"], + add_fields: [ + "sent_or_received","recipients", "subject", + "communication_medium", "communication_type", + "sender" + ], filters: [["status", "=", "Open"]] }; diff --git a/frappe/email/inbox.py b/frappe/email/inbox.py new file mode 100644 index 0000000000..e40b4fbdb9 --- /dev/null +++ b/frappe/email/inbox.py @@ -0,0 +1,33 @@ +import frappe + +def get_email_accounts(user=None): + if not user: + user = frappe.session.user + + email_accounts = [] + + accounts = frappe.get_all("User Email", filters={ "parent": user }, + fields=["email_account", "email_id"], + distinct=True, order_by="idx") + + if not accounts: + return None + + email_accounts.append({ + "email_account": "Sent", + "email_id": "Sent Mail" + }) + + all_accounts = ",".join([ account.get("email_account") for account in accounts ]) + if len(accounts) > 1: + email_accounts.append({ + "email_account": all_accounts, + "email_id": "All Accounts" + }) + + email_accounts.extend(accounts) + + return { + "email_accounts": email_accounts, + "all_accounts": all_accounts + } \ No newline at end of file diff --git a/frappe/public/build.json b/frappe/public/build.json index 3b350e2813..e3ce55d28d 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -245,6 +245,7 @@ "public/js/frappe/views/calendar/calendar.js", "public/js/frappe/views/image/image_view.js", "public/js/frappe/views/kanban/kanban_view.js", + "public/js/frappe/views/inbox/inbox_view.js", "public/js/frappe/list/header_select_all_like_filter.html", "public/js/frappe/list/item_assigned_to_comment_count.html", @@ -253,6 +254,9 @@ "public/js/frappe/views/image/image_view_item_row.html", "public/js/frappe/views/image/image_view_item_main_head.html", "public/js/frappe/views/image/photoswipe_dom.html", + + "public/js/frappe/views/inbox/inbox_view_item_row.html", + "public/js/frappe/views/inbox/inbox_view_item_main_head.html", "public/js/frappe/views/kanban/kanban_board.html", "public/js/frappe/views/kanban/kanban_column.html", diff --git a/frappe/public/js/frappe/list/list_sidebar.html b/frappe/public/js/frappe/list/list_sidebar.html index eb28a8ae1b..f5d9142622 100644 --- a/frappe/public/js/frappe/list/list_sidebar.html +++ b/frappe/public/js/frappe/list/list_sidebar.html @@ -34,18 +34,16 @@ - {% if(doctype == "Communication") { %} - - {% } %}
  • {%= __("Assigned To Me") %}
  • diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index d1431adc97..1a4a8380d9 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -27,7 +27,7 @@ frappe.views.ListSidebar = Class.extend({ this.setup_assigned_to_me(); this.setup_views(); this.setup_kanban_boards(); - + this.setup_email_inbox(); }, setup_views: function() { var show_list_link = false; @@ -39,6 +39,10 @@ frappe.views.ListSidebar = Class.extend({ } //show link for kanban view this.sidebar.find('.list-link[data-view="Kanban"]').removeClass('hide'); + if(this.doctype === "Communication"){ + this.sidebar.find('.list-link[data-view="Inbox"]').removeClass('hide'); + show_list_link = true; + } if(frappe.treeview_settings[this.doctype]) { this.sidebar.find(".tree-link").removeClass("hide"); @@ -46,11 +50,13 @@ frappe.views.ListSidebar = Class.extend({ this.current_view = 'List'; var route = frappe.get_route(); - if(route.length > 2 && in_list(['Gantt', 'Image', 'Kanban', 'Calendar'], route[2])) { + if(route.length > 2 && in_list(['Gantt', 'Image', 'Kanban', 'Calendar', 'Inbox'], route[2])) { this.current_view = route[2]; if(this.current_view === 'Kanban') { this.kanban_board = route[3]; + } else if (this.current_view === 'Inbox') { + this.email_account = route[3] || frappe.boot.all_accounts; } } @@ -59,7 +65,7 @@ frappe.views.ListSidebar = Class.extend({ .attr('disabled', 'disabled').addClass('disabled'); //enable link for Kanban view - this.sidebar.find('.list-link[data-view="Kanban"] a') + this.sidebar.find('.list-link[data-view="Kanban"] a, .list-link[data-view="Inbox"] a') .attr('disabled', null).removeClass('disabled') // show image link if image_view @@ -234,6 +240,30 @@ frappe.views.ListSidebar = Class.extend({ } }); }, + setup_email_inbox: function() { + // get active email account for the user and add in dropdown + if(this.doctype != "Communication") + return; + + var $dropdown = this.page.sidebar.find('.email-account-dropdown'); + var divider = false; + accounts = frappe.boot.email_accounts; + + accounts.forEach(function(account) { + var route = ["List", "Communication", "Inbox", account.email_account].join('/'); + if(!divider) { + $('').appendTo($dropdown); + divider = true; + } + $('
  • '+account.email_id+'
  • ').appendTo($dropdown); + if(account.email_id === "Sent Mail") + divider = false + }); + + $dropdown.find('.new-email-account').click(function() { + frappe.new_doc("Email Account") + }); + }, setup_assigned_to_me: function() { var me = this; this.page.sidebar.find(".assigned-to-me a").on("click", function() { diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index a13e62dfb8..48f8f3e4e3 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -185,6 +185,8 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ this.list_renderer = new frappe.views.ImageView(opts); } else if (this.current_view === 'Kanban') { this.list_renderer = new frappe.views.KanbanView(opts); + } else if (this.current_view === 'Inbox') { + this.list_renderer = new frappe.views.InboxView(opts) } }, @@ -210,6 +212,9 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ if (us.last_view === 'Kanban') { route.push(us['Kanban'].last_kanban_board); } + + if (us.last_view === 'Inbox') + route.push(us['Inbox'].last_email_account) } frappe.set_route(route); @@ -266,10 +271,13 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ set_filters: function (filters) { var me = this; $.each(filters, function (i, f) { + hidden = false if (f.length === 3) { f = [me.doctype, f[0], f[1], f[2]] + } else if (f.length === 5) { + hidden = f.pop(4) || false } - me.filter_list.add_filter(f[0], f[1], f[2], f[3]); + me.filter_list.add_filter(f[0], f[1], f[2], f[3], hidden); }); }, @@ -447,7 +455,7 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ if (!this.list_renderer.settings.use_route) { var route = frappe.get_route(); - if (route[2] && !in_list(['Image', 'Gantt', 'Kanban', 'Calendar'], route[2])) { + if (route[2] && !in_list(['Image', 'Gantt', 'Kanban', 'Calendar', 'Inbox'], route[2])) { $.each(frappe.utils.get_args_dict_from_url(route[2]), function (key, val) { me.set_filter(key, val, true); }); @@ -750,7 +758,7 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ if (!(this.can_delete || this.list_renderer.settings.selectable)) { return; } - this.$page.on('change', '.list-row-checkbox, .list-select-all', function() { + this.$page.find('.list-row-checkbox').change(function () { me.toggle_delete(); }); // after delete, hide delete button @@ -771,7 +779,7 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ .addClass('btn-danger'); checked_items_status.text( - checked_items.length == 1 + no_of_checked_items == 1 ? __('1 item selected') : __('{0} items selected', [checked_items.length]) ) diff --git a/frappe/public/js/frappe/ui/base_list.js b/frappe/public/js/frappe/ui/base_list.js index 0583564eda..35c3f1d266 100644 --- a/frappe/public/js/frappe/ui/base_list.js +++ b/frappe/public/js/frappe/ui/base_list.js @@ -354,7 +354,7 @@ frappe.ui.BaseList = Class.extend({ } this.wrapper.find('.list-paging-area') - .toggle(values.length > 0 || this.start > 0); + .toggle(values.length > 0|| this.start > 0); // callbacks if (this.onrun) this.onrun(); @@ -411,7 +411,7 @@ frappe.ui.BaseList = Class.extend({ } else { // no filter for this item, // setup one - if (['_user_tags', '_comments', '_assign', '_liked_by'].includes(fieldname)) { + if (['_user_tags', '_comments', '_assign', '_liked_by'].ƒincludes(fieldname)) { this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%'); } else { this.filter_list.add_filter(this.doctype, fieldname, '=', label); diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 4f20bd2c98..95a3fe1543 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -85,10 +85,13 @@ frappe.views.CommunicationComposer = Class.extend({ ]; // add from if user has access to multiple email accounts - if(frappe.boot.email_accounts && frappe.boot.email_accounts.length > 1) { + email_accounts = frappe.boot.email_accounts.filter(function(account, idx){ + return !inList(["All Accounts", "Sent"], account.email_account) + }) + if(frappe.boot.email_accounts && email_accounts.length > 1) { fields = [ {label: __("From"), fieldtype: "Select", reqd: 1, fieldname: "sender", - options: frappe.boot.email_accounts.map(function(e) { return e.email_id; }) } + options: accounts.map(function(e) { return e.email_id; }) } ].concat(fields); } diff --git a/frappe/public/js/frappe/views/inbox/inbox_view.js b/frappe/public/js/frappe/views/inbox/inbox_view.js new file mode 100644 index 0000000000..18a155da25 --- /dev/null +++ b/frappe/public/js/frappe/views/inbox/inbox_view.js @@ -0,0 +1,95 @@ +/** + * frappe.views.EmailInboxView + */ + + frappe.provide("frappe.views"); + +frappe.views.InboxView = frappe.views.ListRenderer.extend({ + name: 'Inbox', + render_view: function(values) { + var me = this; + var email_account = this.get_email_account(); + this.emails = values; + // save email account in user_settings + frappe.model.user_settings.save("Communication", 'Inbox', { + last_email_account: email_account + }); + + this.render_inbox_view(); + }, + render_inbox_view: function() { + var html = this.emails.map(this.render_email_row.bind(this)).join(""); + this.container = $('
    ') + .addClass('inbox-container') + .appendTo(this.wrapper); + this.container.append(html); + }, + render_email_row: function(email) { + return frappe.render_template("inbox_view_item_row", { + data: email, + is_sent_emails: this.is_sent_emails, + }); + }, + set_defaults: function() { + this._super(); + this.show_no_result = false; + this.page_title = __("Email Inbox"); + }, + + init_settings: function() { + this._super(); + this.filters = this.get_inbox_filters(); + }, + should_refresh: function() { + var to_refresh = this._super(); + if(!to_refresh) { + this.last_email_account = this.current_email_account || ''; + this.current_email_account = this.get_email_account(); + this.is_sent_emails = this.current_email_account === "Sent"? true: false + + to_refresh = this.current_email_account !== this.last_email_account; + } + + if(to_refresh){ + this.list_view.page.main.find(".list-headers").empty(); + this.list_view.init_headers(); + } + return to_refresh; + }, + get_inbox_filters: function() { + var email_account = this.get_email_account(); + var default_filters = [ + ["Communication", "communication_type", "=", "Communication", true], + ["Communication", "communication_medium", "=", "Email", true], + + ] + var filters = [] + if (email_account === "Sent") + filters = default_filters.concat([ + ["Communication", "sent_or_received", "=", "Sent", true] + ]) + else + filters = default_filters.concat([ + ["Communication", "sent_or_received", "=", "Received", true], + ["Communication", "email_account", "=", email_account, true] + ]) + + return filters + }, + get_header_html: function() { + var header = frappe.render_template('inbox_view_item_main_head', { + _checkbox: ((frappe.model.can_delete(this.doctype) || this.settings.selectable) + && !this.no_delete), + is_sent_emails: this.is_sent_emails + }); + return header; + }, + get_email_account: function() { + var route = frappe.get_route(); + if(!route[3] || !frappe.boot.email_accounts.find(b => b.email_account === route[3])) { + frappe.throw(__(`Email Account ${route[3] || ''} not found`)); + return; + } + return route[3]; + } +}); diff --git a/frappe/public/js/frappe/views/inbox/inbox_view_item_main_head.html b/frappe/public/js/frappe/views/inbox/inbox_view_item_main_head.html new file mode 100644 index 0000000000..40ebaa94cf --- /dev/null +++ b/frappe/public/js/frappe/views/inbox/inbox_view_item_main_head.html @@ -0,0 +1,24 @@ +
    +
    +
    + +
    +
    +
    + {% if (_checkbox) { %} + + {% } %} + {%= __("Subject") %} +
    +
    + +
    +
    + +
    +
    \ No newline at end of file diff --git a/frappe/public/js/frappe/views/inbox/inbox_view_item_row.html b/frappe/public/js/frappe/views/inbox/inbox_view_item_row.html new file mode 100644 index 0000000000..f7c2431a3e --- /dev/null +++ b/frappe/public/js/frappe/views/inbox/inbox_view_item_row.html @@ -0,0 +1,34 @@ +
    +
    +
    +
    +
    + + {% if (data._checkbox) { %} + + {% } %} + + {%= data.subject %} + + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    \ No newline at end of file