@@ -12,6 +12,7 @@ import frappe.desk.desk_page | |||||
from frappe.desk.form.load import get_meta_bundle | from frappe.desk.form.load import get_meta_bundle | ||||
from frappe.utils.change_log import get_versions | from frappe.utils.change_log import get_versions | ||||
from frappe.translate import get_lang_dict | 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 | from frappe.core.doctype.feedback_trigger.feedback_trigger import get_enabled_feedback_trigger | ||||
def get_bootinfo(): | def get_bootinfo(): | ||||
@@ -66,10 +67,9 @@ def get_bootinfo(): | |||||
bootinfo.error_report_email = frappe.get_hooks("error_report_email") | bootinfo.error_report_email = frappe.get_hooks("error_report_email") | ||||
bootinfo.calendars = sorted(frappe.get_hooks("calendars")) | bootinfo.calendars = sorted(frappe.get_hooks("calendars")) | ||||
bootinfo.treeviews = frappe.get_hooks("treeviews") or [] | 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.lang_dict = get_lang_dict() | ||||
bootinfo.feedback_triggers = get_enabled_feedback_trigger() | bootinfo.feedback_triggers = get_enabled_feedback_trigger() | ||||
bootinfo.update(get_email_accounts(user=frappe.session.user)) | |||||
return bootinfo | return bootinfo | ||||
@@ -1,4 +1,8 @@ | |||||
frappe.listview_settings['Communication'] = { | 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"]] | filters: [["status", "=", "Open"]] | ||||
}; | }; |
@@ -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 | |||||
} |
@@ -245,6 +245,7 @@ | |||||
"public/js/frappe/views/calendar/calendar.js", | "public/js/frappe/views/calendar/calendar.js", | ||||
"public/js/frappe/views/image/image_view.js", | "public/js/frappe/views/image/image_view.js", | ||||
"public/js/frappe/views/kanban/kanban_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/header_select_all_like_filter.html", | ||||
"public/js/frappe/list/item_assigned_to_comment_count.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_row.html", | ||||
"public/js/frappe/views/image/image_view_item_main_head.html", | "public/js/frappe/views/image/image_view_item_main_head.html", | ||||
"public/js/frappe/views/image/photoswipe_dom.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_board.html", | ||||
"public/js/frappe/views/kanban/kanban_column.html", | "public/js/frappe/views/kanban/kanban_column.html", | ||||
@@ -34,18 +34,16 @@ | |||||
</ul> | </ul> | ||||
</div> | </div> | ||||
</li> | </li> | ||||
{% if(doctype == "Communication") { %} | |||||
<li class="hide list-link" data-view="Email Inbox"> | |||||
<li class="hide list-link" data-view="Inbox"> | |||||
<div class="btn-group"> | <div class="btn-group"> | ||||
<a class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | <a class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||||
{{ __("Email Inbox") }} <span class="caret"></span> | {{ __("Email Inbox") }} <span class="caret"></span> | ||||
</a> | </a> | ||||
<ul class="dropdown-menu inbox-dropdown" style="max-height: 300px; overflow-y: auto;"> | |||||
<li class="new-kanban-board"><a>{{ __("New Kanban Board") }}</a></li> | |||||
<ul class="dropdown-menu email-account-dropdown" style="max-height: 300px; overflow-y: auto;"> | |||||
<li class="new-email-account"><a>{{ __("New Email Account") }}</a></li> | |||||
</ul> | </ul> | ||||
</div> | </div> | ||||
</li> | </li> | ||||
{% } %} | |||||
<li class="assigned-to-me"> | <li class="assigned-to-me"> | ||||
<a>{%= __("Assigned To Me") %}</a> | <a>{%= __("Assigned To Me") %}</a> | ||||
</li> | </li> | ||||
@@ -27,7 +27,7 @@ frappe.views.ListSidebar = Class.extend({ | |||||
this.setup_assigned_to_me(); | this.setup_assigned_to_me(); | ||||
this.setup_views(); | this.setup_views(); | ||||
this.setup_kanban_boards(); | this.setup_kanban_boards(); | ||||
this.setup_email_inbox(); | |||||
}, | }, | ||||
setup_views: function() { | setup_views: function() { | ||||
var show_list_link = false; | var show_list_link = false; | ||||
@@ -39,6 +39,10 @@ frappe.views.ListSidebar = Class.extend({ | |||||
} | } | ||||
//show link for kanban view | //show link for kanban view | ||||
this.sidebar.find('.list-link[data-view="Kanban"]').removeClass('hide'); | 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]) { | if(frappe.treeview_settings[this.doctype]) { | ||||
this.sidebar.find(".tree-link").removeClass("hide"); | this.sidebar.find(".tree-link").removeClass("hide"); | ||||
@@ -46,11 +50,13 @@ frappe.views.ListSidebar = Class.extend({ | |||||
this.current_view = 'List'; | this.current_view = 'List'; | ||||
var route = frappe.get_route(); | 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]; | this.current_view = route[2]; | ||||
if(this.current_view === 'Kanban') { | if(this.current_view === 'Kanban') { | ||||
this.kanban_board = route[3]; | 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'); | .attr('disabled', 'disabled').addClass('disabled'); | ||||
//enable link for Kanban view | //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') | .attr('disabled', null).removeClass('disabled') | ||||
// show image link if image_view | // 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) { | |||||
$('<li role="separator" class="divider"></li>').appendTo($dropdown); | |||||
divider = true; | |||||
} | |||||
$('<li><a href="#'+ route + '">'+account.email_id+'</a></li>').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() { | setup_assigned_to_me: function() { | ||||
var me = this; | var me = this; | ||||
this.page.sidebar.find(".assigned-to-me a").on("click", function() { | this.page.sidebar.find(".assigned-to-me a").on("click", function() { | ||||
@@ -185,6 +185,8 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ | |||||
this.list_renderer = new frappe.views.ImageView(opts); | this.list_renderer = new frappe.views.ImageView(opts); | ||||
} else if (this.current_view === 'Kanban') { | } else if (this.current_view === 'Kanban') { | ||||
this.list_renderer = new frappe.views.KanbanView(opts); | 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') { | if (us.last_view === 'Kanban') { | ||||
route.push(us['Kanban'].last_kanban_board); | route.push(us['Kanban'].last_kanban_board); | ||||
} | } | ||||
if (us.last_view === 'Inbox') | |||||
route.push(us['Inbox'].last_email_account) | |||||
} | } | ||||
frappe.set_route(route); | frappe.set_route(route); | ||||
@@ -266,10 +271,13 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ | |||||
set_filters: function (filters) { | set_filters: function (filters) { | ||||
var me = this; | var me = this; | ||||
$.each(filters, function (i, f) { | $.each(filters, function (i, f) { | ||||
hidden = false | |||||
if (f.length === 3) { | if (f.length === 3) { | ||||
f = [me.doctype, f[0], f[1], f[2]] | 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) { | if (!this.list_renderer.settings.use_route) { | ||||
var route = frappe.get_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) { | $.each(frappe.utils.get_args_dict_from_url(route[2]), function (key, val) { | ||||
me.set_filter(key, val, true); | 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)) { | if (!(this.can_delete || this.list_renderer.settings.selectable)) { | ||||
return; | return; | ||||
} | } | ||||
this.$page.on('change', '.list-row-checkbox, .list-select-all', function() { | |||||
this.$page.find('.list-row-checkbox').change(function () { | |||||
me.toggle_delete(); | me.toggle_delete(); | ||||
}); | }); | ||||
// after delete, hide delete button | // after delete, hide delete button | ||||
@@ -771,7 +779,7 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ | |||||
.addClass('btn-danger'); | .addClass('btn-danger'); | ||||
checked_items_status.text( | checked_items_status.text( | ||||
checked_items.length == 1 | |||||
no_of_checked_items == 1 | |||||
? __('1 item selected') | ? __('1 item selected') | ||||
: __('{0} items selected', [checked_items.length]) | : __('{0} items selected', [checked_items.length]) | ||||
) | ) | ||||
@@ -354,7 +354,7 @@ frappe.ui.BaseList = Class.extend({ | |||||
} | } | ||||
this.wrapper.find('.list-paging-area') | this.wrapper.find('.list-paging-area') | ||||
.toggle(values.length > 0 || this.start > 0); | |||||
.toggle(values.length > 0|| this.start > 0); | |||||
// callbacks | // callbacks | ||||
if (this.onrun) this.onrun(); | if (this.onrun) this.onrun(); | ||||
@@ -411,7 +411,7 @@ frappe.ui.BaseList = Class.extend({ | |||||
} else { | } else { | ||||
// no filter for this item, | // no filter for this item, | ||||
// setup one | // 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 + '%'); | this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%'); | ||||
} else { | } else { | ||||
this.filter_list.add_filter(this.doctype, fieldname, '=', label); | this.filter_list.add_filter(this.doctype, fieldname, '=', label); | ||||
@@ -85,10 +85,13 @@ frappe.views.CommunicationComposer = Class.extend({ | |||||
]; | ]; | ||||
// add from if user has access to multiple email accounts | // 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 = [ | fields = [ | ||||
{label: __("From"), fieldtype: "Select", reqd: 1, fieldname: "sender", | {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); | ].concat(fields); | ||||
} | } | ||||
@@ -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 = $('<div>') | |||||
.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 <b>${route[3] || ''}</b> not found`)); | |||||
return; | |||||
} | |||||
return route[3]; | |||||
} | |||||
}); |
@@ -0,0 +1,24 @@ | |||||
<div class="list-row list-row-head" data-list-renderer="Inbox"> | |||||
<div class="row doclist-row"> | |||||
<div class="col-sm-10 list-row-left"> | |||||
<!-- title + columns --> | |||||
<div class="row"> | |||||
<div class="col-sm-8 col-xs-12 list-col ellipsis h6 text-muted"> | |||||
<div class="list-value"> | |||||
{% if (_checkbox) { %} | |||||
<input class="list-select-all" type="checkbox" | |||||
title="{%= __("Select All") %}"> | |||||
{% } %} | |||||
<span class="list-col-title">{%= __("Subject") %}</span> | |||||
</div> | |||||
</div> | |||||
<div class="col-sm-4 hidden-xs list-col ellipsis h6 text-muted"> | |||||
<div class="list-value"> | |||||
<span class="list-col-title">{%= __(is_sent_emails ? "To": "From") %}</span> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="col-sm-2 hidden-xs list-row-right"></div> | |||||
</div> | |||||
</div> |
@@ -0,0 +1,34 @@ | |||||
<div class="list-row"> | |||||
<div class="row doclist-row {% if (data._checkbox) { %} has-checkbox {% } %}"> | |||||
<div class="col-sm-10 col-xs-10 list-row-left"> | |||||
<div class="row"> | |||||
<div class="col-sm-8 list-col ellipsis h6 text-muted"> | |||||
<span class="list-value"> | |||||
{% if (data._checkbox) { %} | |||||
<input class="list-row-checkbox" type="checkbox" data-name="{{data.name}}"> | |||||
{% } %} | |||||
<a class="grey list-id" href="#Form/{%= data._doctype_encoded %}/{%= data._name_encoded %}"> | |||||
{%= data.subject %} | |||||
</a> | |||||
</span> | |||||
</div> | |||||
<div class="col-sm-4 hidden-xs list-col ellipsis h6 text-muted"> | |||||
<span class="filterable text-muted" data-filter="sender,=,{%= data.sender %}"> | |||||
{%= is_sent_emails? data.recipients: data.sender %} | |||||
</span> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="col-sm-2 col-xs-2 text-right list-row-right" style="padding-left:0px"> | |||||
<div class="visible-xs"> | |||||
<span class="text-muted"><i class="fa fa-paperclip fa-large"></i></span> | |||||
</div> | |||||
<div class="hidden-xs"> | |||||
<span class="list-row-modified text-muted"><i class="fa fa-paperclip fa-large"></i></span> | |||||
<span class="list-row-modified text-muted"> | |||||
{%= comment_when(data.modified, true) %} | |||||
</span> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> |