diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js index fcd4326eb80f..083e1a4beedd 100644 --- a/frappe/public/js/frappe/form/footer/form_timeline.js +++ b/frappe/public/js/frappe/form/footer/form_timeline.js @@ -422,13 +422,13 @@ class FormTimeline extends BaseTimeline { } if (this.frm.doctype === "Communication") { - args.txt = ""; + args.message = ""; args.last_email = this.frm.doc; args.recipients = this.frm.doc.sender; args.subject = __("Re: {0}", [this.frm.doc.subject]); } else { const comment_value = frappe.markdown(this.frm.comment_box.get_value()); - args.txt = strip_html(comment_value) ? comment_value : ''; + args.message = strip_html(comment_value) ? comment_value : ''; } new frappe.views.CommunicationComposer(args); diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index df56fe7b8448..5aa590d281be 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -130,6 +130,9 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({ } }); }, + has_field: function(fieldname) { + return !!this.fields_dict[fieldname]; + }, set_input: function(key, val) { return this.set_value(key, val); }, diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index b33cd1a3f181..b6dd4cac263a 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -34,7 +34,7 @@ frappe.views.CommunicationComposer = class { minimizable: true }); - this.dialog.sections[0].wrapper.addClass('to_section'); + $(this.dialog.$wrapper.find(".form-section").get(0)).addClass('to_section'); this.prepare(); this.dialog.show(); @@ -98,6 +98,17 @@ frappe.views.CommunicationComposer = class { 300 ) }, + { + fieldtype: "Button", + label: __("Add Signature"), + fieldname: 'add_signature', + hidden: 1, + click: async function () { + let sender_email = this.dialog.get_value('sender') || ""; + this.content_set = false; + await this.set_content(sender_email); + }.bind(this) + }, { fieldtype: "Section Break" }, { label: __("Send me a copy"), @@ -144,15 +155,21 @@ frappe.views.CommunicationComposer = class { }); if (email_accounts.length) { + this.user_email_accounts = email_accounts.map(function(e) { + return e.email_id; + }); + fields.unshift({ label: __("From"), fieldtype: "Select", reqd: 1, fieldname: "sender", - options: email_accounts.map(function(e) { - return e.email_id; - }) + options: this.user_email_accounts }); + //Preselect email senders if there is only one + if (this.user_email_accounts.length==1) { + this['sender'] = this.user_email_accounts + } } return fields; @@ -175,9 +192,15 @@ frappe.views.CommunicationComposer = class { this.setup_email(); this.setup_email_template(); this.setup_last_edited_communication(); + this.setup_add_signature_button(); this.set_values(); } + setup_add_signature_button() { + let has_sender = this.dialog.has_field('sender'); + this.dialog.set_df_property('add_signature', 'hidden', !has_sender); + } + setup_multiselect_queries() { ['recipients', 'cc', 'bcc'].forEach(field => { this.dialog.fields_dict[field].get_data = () => { @@ -237,7 +260,9 @@ frappe.views.CommunicationComposer = class { // 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 const identifier = `#${this.frm.doc.name}`; - if (!this.subject.includes(identifier)) { + + // converting to str for int names + if (!cstr(this.subject).includes(identifier)) { this.subject = `${this.subject} (${identifier})`; } } @@ -261,7 +286,6 @@ frappe.views.CommunicationComposer = class { const subject_field = me.dialog.fields_dict.subject; let content = content_field.get_value() || ""; - content = content.split('')[1] || content; content_field.set_value(`${reply.message}
${content}`); subject_field.set_value(reply.subject); @@ -349,7 +373,7 @@ frappe.views.CommunicationComposer = class { } async set_values_from_last_edited_communication() { - if (this.txt || this.message) return; + if (this.message) return; const last_edited = this.get_last_edited_communication(); if (!last_edited.content) return; @@ -708,10 +732,10 @@ frappe.views.CommunicationComposer = class { } } - async set_content() { + async set_content(sender_email) { if (this.content_set) return; - let message = this.txt || this.message || ""; + let message = this.message || ""; if (!message && this.frm) { const { doctype, docname } = this.frm; message = await localforage.getItem(doctype + docname) || ""; @@ -721,35 +745,47 @@ frappe.views.CommunicationComposer = class { this.content_set = true; } - message += await this.get_signature(); - - const SALUTATION_END_COMMENT = ""; - if (this.real_name && !message.includes(SALUTATION_END_COMMENT)) { - message = ` -

${__('Dear {0},', [this.real_name], 'Salutation in new email')},

- ${SALUTATION_END_COMMENT}
- ${message} - `; - } + message += await this.get_signature(sender_email || null); - if (this.is_a_reply) { + if (this.is_a_reply && !this.reply_set) { message += this.get_earlier_reply(); } await this.dialog.set_value("content", message); } - async get_signature() { + async get_signature(sender_email) { let signature = frappe.boot.user.email_signature; if (!signature) { - const response = await frappe.db.get_value( - 'Email Account', - {'default_outgoing': 1, 'add_signature': 1}, - 'signature' - ); + let filters = { + 'add_signature': 1 + }; + + if (sender_email) { + filters['email_id'] = sender_email; + } else { + filters['default_outgoing'] = 1; + } + + const email_accounts = await frappe.db.get_list("Email Account", { + filters: filters, + fields: ['signature', 'email_id'], + limit: 1 + }); - signature = response.message.signature; + let filtered_email = null; + if (email_accounts.length) { + signature = email_accounts[0].signature; + filtered_email = email_accounts[0].email_id; + } + + if (!sender_email && filtered_email) { + if (this.user_email_accounts && + this.user_email_accounts.includes(filtered_email)) { + this.dialog.set_value('sender', filtered_email); + } + } } if (!signature) return ""; @@ -758,10 +794,12 @@ frappe.views.CommunicationComposer = class { signature = signature.replace(/\n/g, "
"); } - return "
" + signature; + return "
" + signature; } get_earlier_reply() { + this.reply_set = false; + const last_email = ( this.last_email || this.frm && this.frm.timeline.get_last_email(true) @@ -785,6 +823,8 @@ frappe.views.CommunicationComposer = class { last_email.communication_date || last_email.creation ); + this.reply_set = true; + return `

${separator_element || ''} @@ -799,12 +839,12 @@ frappe.views.CommunicationComposer = class { html2text(html) { // convert HTML to text and try and preserve whitespace - 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'); + html = html + .replace(/<\/div>/g, "
") // replace end of blocks + .replace(/<\/p>/g, "

") // replace end of paragraphs + .replace(/
/g, "\n"); - // replace multiple empty lines with just one - return d.textContent.replace(/\n{3,}/g, '\n\n'); + const text = frappe.utils.html2text(html); + return text.replace(/\n{3,}/g, "\n\n"); } -}; +}; \ No newline at end of file diff --git a/frappe/public/scss/common/modal.scss b/frappe/public/scss/common/modal.scss index 44510b4b4328..b6208e6e2f6b 100644 --- a/frappe/public/scss/common/modal.scss +++ b/frappe/public/scss/common/modal.scss @@ -210,16 +210,22 @@ body.modal-open[style^="padding-right"] { form { display: flex; align-items: center; - .frappe-control:first-child { - flex: 1; - margin-bottom: 0px; - } - .frappe-control:last-child { - margin-left: 10px; - margin-bottom: -24px; - button { - // same as form-control input - height: calc(1.5em + .75rem + 2px); + + .frappe-control { + &[data-fieldname="sender"] { + flex: 1; + margin-bottom: 0px; + } + &[data-fieldname="recipients"] { + margin-left: 10px; + } + &[data-fieldname="option_toggle_button"] { + margin-left: 10px; + margin-bottom: -24px; + button { + // same as form-control input + height: calc(1.5em + .75rem + 2px); + } } } }