diff --git a/frappe/public/js/frappe/form/controls/multiselect.js b/frappe/public/js/frappe/form/controls/multiselect.js index 64ca4fc83d..bbd7aef822 100644 --- a/frappe/public/js/frappe/form/controls/multiselect.js +++ b/frappe/public/js/frappe/form/controls/multiselect.js @@ -68,7 +68,7 @@ frappe.ui.form.ControlMultiSelect = frappe.ui.form.ControlAutocomplete.extend({ let data; if(this.df.get_data) { data = this.df.get_data(); - this.set_data(data); + if (data) this.set_data(data); } else { data = this._super(); } diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 6e70284619..cd7f0197c8 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -2,16 +2,15 @@ // MIT License. See license.txt frappe.last_edited_communication = {}; -frappe.standard_replies = {}; -frappe.separator_element = '
---
'; +const separator_element = '
---
'; frappe.views.CommunicationComposer = Class.extend({ - init: function(opts) { + init(opts) { $.extend(this, opts); this.make(); }, - make: function() { - var me = this; + make() { + const me = this; this.dialog = new frappe.ui.Dialog({ title: (this.title || this.subject || __("New Email")), @@ -19,56 +18,35 @@ frappe.views.CommunicationComposer = Class.extend({ fields: this.get_fields(), primary_action_label: __("Send"), size: 'large', - primary_action: function() { - me.delete_saved_draft(); + primary_action() { me.send_action(); + me.clear_cache(); + }, + secondary_action_label: __("Discard"), + secondary_action() { + me.dialog.hide(); + me.clear_cache(); }, minimizable: true }); this.dialog.sections[0].wrapper.addClass('to_section'); - ['recipients', 'cc', 'bcc'].forEach(field => { - this.dialog.fields_dict[field].get_data = function() { - const data = me.dialog.fields_dict[field].get_value(); - const txt = data.match(/[^,\s*]*$/)[0] || ''; - let options = []; - - frappe.call({ - method: "frappe.email.get_contact_list", - args: { - txt: txt, - }, - callback: (r) => { - options = r.message; - me.dialog.fields_dict[field].set_data(options); - } - }); - return options; - } - }); - this.prepare(); this.dialog.show(); if (this.frm) { $(document).trigger('form-typing', [this.frm]); } - - if (this.cc || this.bcc) { - this.toggle_more_options(true); - } }, - get_fields: function() { - let contactList = []; - let fields = [ + get_fields() { + const fields = [ { label: __("To"), fieldtype: "MultiSelect", reqd: 0, fieldname: "recipients", - options: contactList }, { fieldtype: "Button", @@ -87,13 +65,11 @@ frappe.views.CommunicationComposer = Class.extend({ label: __("CC"), fieldtype: "MultiSelect", fieldname: "cc", - options: contactList }, { label: __("BCC"), fieldtype: "MultiSelect", fieldname: "bcc", - options: contactList }, { label: __("Email Template"), @@ -163,18 +139,16 @@ frappe.views.CommunicationComposer = Class.extend({ ); }); - if (frappe.boot.email_accounts && email_accounts.length > 1) { - fields = [ - { - label: __("From"), - fieldtype: "Select", - reqd: 1, - fieldname: "sender", - options: email_accounts.map(function(e) { - return e.email_id; - }) - } - ].concat(fields); + if (email_accounts.length > 1) { + fields.unshift({ + label: __("From"), + fieldtype: "Select", + reqd: 1, + fieldname: "sender", + options: email_accounts.map(function(e) { + return e.email_id; + }) + }); } return fields; @@ -183,56 +157,58 @@ frappe.views.CommunicationComposer = Class.extend({ toggle_more_options(show_options) { show_options = show_options || this.dialog.fields_dict.more_options.df.hidden; this.dialog.set_df_property('more_options', 'hidden', !show_options); - let label = frappe.utils.icon(show_options ? 'up-line': 'down'); + + const label = frappe.utils.icon(show_options ? 'up-line': 'down'); this.dialog.get_field('option_toggle_button').set_label(label); }, - prepare: function() { + prepare() { + this.setup_multiselect_queries(); this.setup_subject_and_recipients(); this.setup_print_language(); this.setup_print(); this.setup_attach(); this.setup_email(); - this.setup_last_edited_communication(); this.setup_email_template(); + this.setup_last_edited_communication(); + this.set_values(); + }, - this.dialog.set_value("recipients", this.recipients || ''); - this.dialog.set_value("cc", this.cc || ''); - this.dialog.set_value("bcc", this.bcc || ''); - - if(this.dialog.fields_dict.sender) { - this.dialog.fields_dict.sender.set_value(this.sender || ''); - } - this.dialog.fields_dict.subject.set_value( - frappe.utils.html2text(this.subject) || '' - ); - - this.setup_earlier_reply(); + setup_multiselect_queries() { + ['recipients', 'cc', 'bcc'].forEach(field => { + this.dialog.fields_dict[field].get_data = () => { + const data = this.dialog.fields_dict[field].get_value(); + const txt = data.match(/[^,\s*]*$/)[0] || ''; - if ('frm' in this && !this.is_a_reply) { - // set default email template for the first email in a document - this.dialog.set_value("email_template", this.frm.meta.default_email_template || ''); - } + frappe.call({ + method: "frappe.email.get_contact_list", + args: {txt}, + callback: (r) => { + this.dialog.fields_dict[field].set_data(r.message); + } + }); + } + }); }, - setup_subject_and_recipients: function() { + setup_subject_and_recipients() { this.subject = this.subject || ""; - if(!this.forward && !this.recipients && this.last_email) { + if (!this.forward && !this.recipients && this.last_email) { this.recipients = this.last_email.sender; this.cc = this.last_email.cc; this.bcc = this.last_email.bcc; } - if(!this.forward && !this.recipients) { + if (!this.forward && !this.recipients) { this.recipients = this.frm && this.frm.timeline.get_recipient(); } - if(!this.subject && this.frm) { + if (!this.subject && this.frm) { // get subject from last communication - var last = this.frm.timeline.get_last_email(); + const last = this.frm.timeline.get_last_email(); - if(last) { + if (last) { this.subject = last.subject; if(!this.recipients) { this.recipients = last.sender; @@ -256,7 +232,7 @@ frappe.views.CommunicationComposer = Class.extend({ // always add an identifier to catch a reply // some email clients (outlook) may not send the message id to identify // the thread. So as a backup we use the name of the document as identifier - let identifier = `#${this.frm.doc.name}`; + const identifier = `#${this.frm.doc.name}`; if (!this.subject.includes(identifier)) { this.subject = `${this.subject} (${identifier})`; } @@ -267,34 +243,23 @@ frappe.views.CommunicationComposer = Class.extend({ } }, - setup_email_template: function() { - var me = this; + setup_email_template() { + const me = this; this.dialog.fields_dict["email_template"].df.onchange = () => { - var email_template = me.dialog.fields_dict.email_template.get_value(); - if (email_template === '') { - return; - } - - var prepend_reply = function(reply) { - if(me.reply_added===email_template) { - return; - } - var content_field = me.dialog.fields_dict.content; - var subject_field = me.dialog.fields_dict.subject; - var content = content_field.get_value() || ""; - var subject = subject_field.get_value() || ""; + const email_template = me.dialog.fields_dict.email_template.get_value(); + if (!email_template) return; - var parts = content.split(''); + function prepend_reply(reply) { + if (me.reply_added === email_template) return; - if(parts.length===2) { - content = [reply.message, "
", parts[1]]; - } else { - content = [reply.message, "
", content]; - } + const content_field = me.dialog.fields_dict.content; + const subject_field = me.dialog.fields_dict.subject; - content_field.set_value(content.join('')); + let content = content_field.get_value() || ""; + content = content.split('')[1] || content; + content_field.set_value(`${reply.message}
${content}`); subject_field.set_value(reply.subject); me.reply_added = email_template; @@ -307,83 +272,105 @@ frappe.views.CommunicationComposer = Class.extend({ doc: me.frm.doc, _lang: me.dialog.get_value("language_sel") }, - callback: function(r) { + callback(r) { prepend_reply(r.message); }, }); } }, - setup_last_edited_communication: function() { - var me = this; - if (!this.doc){ - if (cur_frm){ - this.doc = cur_frm.doctype; - }else{ - this.doc = "Inbox"; - } - } - if (cur_frm && cur_frm.docname) { - this.key = cur_frm.docname; + setup_last_edited_communication() { + if (this.frm) { + this.doctype = this.frm.doctype; + this.key = this.frm.docname; } else { - this.key = "Inbox"; + this.doctype = this.key = "Inbox"; } - if(this.last_email) { + + if (this.last_email) { this.key = this.key + ":" + this.last_email.name; } - if(this.subject){ + + if (this.subject) { this.key = this.key + ":" + this.subject; } - this.dialog.onhide = function() { - var last_edited_communication = me.get_last_edited_communication(); - $.extend(last_edited_communication, { - sender: me.dialog.get_value("sender"), - recipients: me.dialog.get_value("recipients"), - cc: me.dialog.get_value("cc"), - bcc: me.dialog.get_value("bcc"), - subject: me.dialog.get_value("subject"), - content: me.dialog.get_value("content"), - }); - if (me.frm) { - $(document).trigger("form-stopped-typing", [me.frm]); + this.dialog.on_hide = () => { + $.extend( + this.get_last_edited_communication(true), + this.dialog.get_values(true) + ); + + if (this.frm) { + $(document).trigger("form-stopped-typing", [this.frm]); } } + }, - this.dialog.on_page_show = function() { - if (!me.txt) { - var last_edited_communication = me.get_last_edited_communication(); - if(last_edited_communication.content) { - me.dialog.set_value("sender", last_edited_communication.sender || ""); - me.dialog.set_value("subject", last_edited_communication.subject || ""); - me.dialog.set_value("recipients", last_edited_communication.recipients || ""); - me.dialog.set_value("cc", last_edited_communication.cc || ""); - me.dialog.set_value("bcc", last_edited_communication.bcc || ""); - me.dialog.set_value("content", last_edited_communication.content || ""); - } - } + get_last_edited_communication(clear) { + if (!frappe.last_edited_communication[this.doctype]) { + frappe.last_edited_communication[this.doctype] = {}; + } + if (clear || !frappe.last_edited_communication[this.doctype][this.key]) { + frappe.last_edited_communication[this.doctype][this.key] = {}; + console.log('cleared!'); } + return frappe.last_edited_communication[this.doctype][this.key]; }, - get_last_edited_communication: function() { - if (!frappe.last_edited_communication[this.doc]) { - frappe.last_edited_communication[this.doc] = {}; + set_values: async function () { + for (const fieldname of ["recipients", "cc", "bcc", "sender"]) { + await this.dialog.set_value(fieldname, this[fieldname] || ""); } - if(!frappe.last_edited_communication[this.doc][this.key]) { - frappe.last_edited_communication[this.doc][this.key] = {}; + const subject = frappe.utils.html2text(this.subject) || ''; + await this.dialog.set_value("subject", subject); + + await this.set_values_from_last_edited_communication(); + await this.set_content(); + + // set default email template for the first email in a document + if (this.frm && !this.is_a_reply && !this.content_set) { + const email_template = this.frm.meta.default_email_template || ''; + await this.dialog.set_value("email_template", email_template); } - return frappe.last_edited_communication[this.doc][this.key]; + for (const fieldname of ['email_template', 'cc', 'bcc']) { + if (this.dialog.get_value(fieldname)) { + this.toggle_more_options(true); + break; + } + } }, - selected_format: function() { - return this.dialog.fields_dict.select_print_format.input.value || (this.frm && this.frm.meta.default_print_format) || "Standard"; + set_values_from_last_edited_communication: async function () { + if (this.txt) return; + + const last_edited = this.get_last_edited_communication(); + if (!last_edited.content) return; + + // prevent re-triggering of email template + if (last_edited.email_template) { + const template_field = this.dialog.fields_dict.email_template; + await template_field.set_model_value(last_edited.email_template); + delete last_edited.email_template; + } + + await this.dialog.set_values(last_edited); + this.content_set = true; }, - get_print_format: function(format) { + selected_format() { + return ( + this.dialog.fields_dict.select_print_format.input.value + || this.frm && this.frm.meta.default_print_format + || "Standard" + ); + }, + + get_print_format(format) { if (!format) { format = this.selected_format(); } @@ -395,9 +382,9 @@ frappe.views.CommunicationComposer = Class.extend({ } }, - setup_print_language: function() { - var doc = this.doc || cur_frm.doc; - var fields = this.dialog.fields_dict; + setup_print_language() { + const doc = this.frm && this.frm.doc; + const fields = this.dialog.fields_dict; //Load default print language from doctype this.lang_code = doc.language @@ -407,7 +394,7 @@ frappe.views.CommunicationComposer = Class.extend({ } //On selection of language retrieve language code - var me = this; + const me = this; $(fields.language_sel.input).change(function(){ me.lang_code = this.value }) @@ -422,9 +409,9 @@ frappe.views.CommunicationComposer = Class.extend({ } }, - setup_print: function() { + setup_print() { // print formats - var fields = this.dialog.fields_dict; + const fields = this.dialog.fields_dict; // toggle print format $(fields.attach_document_print.input).click(function() { @@ -434,8 +421,8 @@ frappe.views.CommunicationComposer = Class.extend({ // select print format $(fields.select_print_format.wrapper).toggle(false); - if (cur_frm) { - const print_formats = frappe.meta.get_print_formats(cur_frm.meta.name); + if (this.frm) { + const print_formats = frappe.meta.get_print_formats(this.frm.meta.name); $(fields.select_print_format.input) .empty() .add_options(print_formats) @@ -446,9 +433,9 @@ frappe.views.CommunicationComposer = Class.extend({ }, - setup_attach: function() { - var fields = this.dialog.fields_dict; - var attach = $(fields.select_attachments.wrapper); + setup_attach() { + const fields = this.dialog.fields_dict; + const attach = $(fields.select_attachments.wrapper); if (!this.attachments) { this.attachments = []; @@ -493,7 +480,7 @@ frappe.views.CommunicationComposer = Class.extend({ this.render_attachment_rows(); }, - render_attachment_rows: function(attachment) { + render_attachment_rows(attachment) { const select_attachments = this.dialog.fields_dict.select_attachments; const attachment_rows = $(select_attachments.wrapper).find(".attach-list"); if (attachment) { @@ -536,9 +523,9 @@ frappe.views.CommunicationComposer = Class.extend({

`); }, - setup_email: function() { + setup_email() { // email - var fields = this.dialog.fields_dict; + const fields = this.dialog.fields_dict; if(this.attach_document_print) { $(fields.attach_document_print.input).click(); @@ -547,21 +534,20 @@ frappe.views.CommunicationComposer = Class.extend({ $(fields.send_me_a_copy.input).on('click', () => { // update send me a copy (make it sticky) - let val = fields.send_me_a_copy.get_value(); + const val = fields.send_me_a_copy.get_value(); frappe.db.set_value('User', frappe.session.user, 'send_me_a_copy', val); frappe.boot.user.send_me_a_copy = val; }); }, - send_action: function() { - var me = this; - var btn = me.dialog.get_primary_btn(); - - var form_values = this.get_values(); + send_action() { + const me = this; + const btn = me.dialog.get_primary_btn(); + const form_values = this.get_values(); if(!form_values) return; - var selected_attachments = + const selected_attachments = $.map($(me.dialog.wrapper).find("[data-file-name]:checked"), function (element) { return $(element).attr("data-file-name"); }); @@ -574,16 +560,16 @@ frappe.views.CommunicationComposer = Class.extend({ } }, - get_values: function() { - var form_values = this.dialog.get_values(); + get_values() { + const form_values = this.dialog.get_values(); // cc - for ( var i=0, l=this.dialog.fields.length; i < l; i++ ) { - var df = this.dialog.fields[i]; + for (let i = 0, l = this.dialog.fields.length; i < l; i++) { + const df = this.dialog.fields[i]; - if ( df.is_cc_checkbox ) { + if (df.is_cc_checkbox) { // concat in cc - if ( form_values[df.fieldname] ) { + if (form_values[df.fieldname]) { form_values.cc = ( form_values.cc ? (form_values.cc + ", ") : "" ) + df.fieldname; form_values.bcc = ( form_values.bcc ? (form_values.bcc + ", ") : "" ) + df.fieldname; } @@ -595,35 +581,40 @@ frappe.views.CommunicationComposer = Class.extend({ return form_values; }, - save_as_draft: function() { + save_as_draft() { if (this.dialog && this.frm) { let message = this.dialog.get_value('content'); - message = message.split(frappe.separator_element)[0]; + message = message.split(separator_element)[0]; localforage.setItem(this.frm.doctype + this.frm.docname, message).catch(e => { if (e) { // silently fail console.log(e); // eslint-disable-line - console.warn('[Communication] localStorage is full. Cannot save message as draft'); // eslint-disable-line + console.warn('[Communication] IndexedDB is full. Cannot save message as draft'); // eslint-disable-line } }); } }, + clear_cache() { + this.delete_saved_draft(); + this.get_last_edited_communication(true); + }, + delete_saved_draft() { if (this.dialog && this.frm) { localforage.removeItem(this.frm.doctype + this.frm.docname).catch(e => { if (e) { // silently fail console.log(e); // eslint-disable-line - console.warn('[Communication] localStorage is full. Cannot save message as draft'); // eslint-disable-line + console.warn('[Communication] IndexedDB is full. Cannot save message as draft'); // eslint-disable-line } }); } }, - send_email: function(btn, form_values, selected_attachments, print_html, print_format) { - var me = this; + send_email(btn, form_values, selected_attachments, print_html, print_format) { + const me = this; me.dialog.hide(); if(!form_values.recipients) { @@ -637,7 +628,7 @@ frappe.views.CommunicationComposer = Class.extend({ } - if(cur_frm && !frappe.model.can_email(me.doc.doctype, cur_frm)) { + if(this.frm && !frappe.model.can_email(me.doc.doctype, this.frm)) { frappe.msgprint(__("You are not allowed to send emails related to this document")); return; } @@ -658,15 +649,17 @@ frappe.views.CommunicationComposer = Class.extend({ send_me_a_copy: form_values.send_me_a_copy, print_format: print_format, sender: form_values.sender, - sender_full_name: form_values.sender?frappe.user.full_name():undefined, + sender_full_name: form_values.sender + ? frappe.user.full_name() + : undefined, email_template: form_values.email_template, attachments: selected_attachments, _lang : me.lang_code, read_receipt:form_values.send_read_receipt, print_letterhead: me.is_print_letterhead_checked(), }, - btn: btn, - callback: function(r) { + btn, + callback(r) { if(!r.exc) { frappe.utils.play_sound("email"); @@ -678,8 +671,8 @@ frappe.views.CommunicationComposer = Class.extend({ if ((frappe.last_edited_communication[me.doc] || {})[me.key]) { delete frappe.last_edited_communication[me.doc][me.key]; } - if (cur_frm) { - cur_frm.reload_doc(); + if (this.frm) { + this.frm.reload_doc(); } // try the success callback if it exists @@ -707,7 +700,7 @@ frappe.views.CommunicationComposer = Class.extend({ }); }, - is_print_letterhead_checked: function() { + is_print_letterhead_checked() { if (this.frm && $(this.frm.wrapper).find('.form-print-wrapper').is(':visible')){ return $(this.frm.wrapper).find('.print-letterhead').prop('checked') ? 1 : 0; } else { @@ -716,96 +709,96 @@ frappe.views.CommunicationComposer = Class.extend({ } }, - get_default_outgoing_email_account_signature: function() { - return frappe.db.get_value('Email Account', { 'default_outgoing': 1, 'add_signature': 1 }, 'signature'); - }, - - setup_earlier_reply: async function() { - let fields = this.dialog.fields_dict; - let signature = frappe.boot.user.email_signature || ""; - - if (!signature) { - const res = await this.get_default_outgoing_email_account_signature(); - signature = res.message.signature ? "" + res.message.signature : ""; - } + set_content: async function() { + if (this.content_set) return; - if (signature && !frappe.utils.is_html(signature)) { - signature = signature.replace(/\n/g, "
"); + let message = this.txt || ""; + if (!message && this.frm) { + const { doctype, docname } = this.frm; + message = await localforage.getItem(doctype + docname) || ""; } - if(this.txt) { - this.message = this.txt + (this.message ? ("

" + this.message) : ""); - } else { - // saved draft in localStorage - const { doctype, docname } = this.frm || {}; - if (doctype && docname) { - this.message = await localforage.getItem(doctype + docname) || ''; - } + if (message) { + this.content_set = true; } - if(this.real_name) { - this.message = '

'+__('Dear') +' ' - + this.real_name + ",


" + (this.message || ""); + message += await this.get_signature(); + if (this.real_name && !message.includes("")) { + message = `

${__('Dear')} ${this.real_name},

+
${message}`; } - if(this.message && signature && this.message.includes(signature)) { - signature = ""; + if (this.is_a_reply) { + message += this.get_earlier_reply(); } - let reply = (this.message || "") + (signature ? ("
" + signature) : ""); - let content = ''; + await this.dialog.set_value("content", message); + }, - if (this.is_a_reply === 'undefined') { - this.is_a_reply = true; - } + get_signature: async function () { + let signature = frappe.boot.user.email_signature; - if (this.is_a_reply) { - let last_email = this.last_email; + if (!signature) { + const response = await frappe.db.get_value( + 'Email Account', + {'default_outgoing': 1, 'add_signature': 1}, + 'signature' + ); - if (!last_email) { - last_email = this.frm && this.frm.timeline.get_last_email(true); - } + signature = response.message.signature; + } - if (!last_email) return; + if (!signature) return ""; - let last_email_content = last_email.original_comment || last_email.content; + if (!frappe.utils.is_html(signature)) { + signature = signature.replace(/\n/g, "
"); + } - // convert the email context to text as we are enclosing - // this inside
- last_email_content = this.html2text(last_email_content).replace(/\n/g, '
'); + return "
" + signature; + }, - // clip last email for a maximum of 20k characters - // to prevent the email content from getting too large - if (last_email_content.length > 20 * 1024) { - last_email_content += '
' + __('Message clipped') + '
' + last_email_content; - last_email_content = last_email_content.slice(0, 20 * 1024); - } + get_earlier_reply() { + const last_email = ( + this.last_email + || this.frm && this.frm.timeline.get_last_email(true) + ); - let communication_date = last_email.communication_date || last_email.creation; - content = ` - ${reply} -

- ${frappe.separator_element || ''} -

${__("On {0}, {1} wrote:", [frappe.datetime.global_date_format(communication_date) , last_email.sender])}

-
- ${last_email_content} -
- `; - } else { - content = reply; - } - fields.content.set_value(content); + if (!last_email) return ""; + let last_email_content = last_email.original_comment || last_email.content; + + // convert the email context to text as we are enclosing + // this inside
+ last_email_content = this.html2text(last_email_content).replace(/\n/g, '
'); + + // clip last email for a maximum of 20k characters + // to prevent the email content from getting too large + if (last_email_content.length > 20 * 1024) { + last_email_content += '
' + __('Message clipped') + '
' + last_email_content; + last_email_content = last_email_content.slice(0, 20 * 1024); + } + + const communication_date = last_email.communication_date || last_email.creation; + return ` +

+ ${separator_element || ''} +

${__("On {0}, {1} wrote:", [ + frappe.datetime.global_date_format(communication_date), + last_email.sender + ])}

+
+ ${last_email_content} +
+ `; }, - html2text: function(html) { + html2text(html) { // convert HTML to text and try and preserve whitespace - var d = document.createElement( 'div' ); + const d = document.createElement( 'div' ); d.innerHTML = html.replace(/<\/div>/g, '
') // replace end of blocks .replace(/<\/p>/g, '

') // replace end of paragraphs .replace(/
/g, '\n'); - let text = d.textContent; // replace multiple empty lines with just one - return text.replace(/\n{3,}/g, '\n\n'); + return d.textContent.replace(/\n{3,}/g, '\n\n'); } });