`);
@@ -37,7 +40,7 @@ class BaseTimeline {
${label}
`);
action_btn.click(action);
- this.timeline_actions_wrapper.append(action_btn);
+ this.timeline_actions_wrapper.find('.action-buttons').append(action_btn);
return action_btn;
}
diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js
index f278d1b64b..d440874f36 100644
--- a/frappe/public/js/frappe/form/footer/form_timeline.js
+++ b/frappe/public/js/frappe/form/footer/form_timeline.js
@@ -77,12 +77,14 @@ class FormTimeline extends BaseTimeline {
const message = __("Add to this activity by mailing to {0}", [link.bold()]);
this.document_email_link_wrapper = $(`
-
+
-
${message}
+
+ ${message}
+
`);
- this.timeline_wrapper.append(this.document_email_link_wrapper);
+ this.timeline_actions_wrapper.append(this.document_email_link_wrapper);
this.document_email_link_wrapper
.find('.document-email-link')
diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js
index 5bc25fbfd5..beefba2e18 100644
--- a/frappe/public/js/frappe/form/form.js
+++ b/frappe/public/js/frappe/form/form.js
@@ -860,36 +860,32 @@ frappe.ui.form.Form = class FrappeForm {
}
_cancel(btn, callback, on_error, skip_confirm) {
+ const me = this;
const cancel_doc = () => {
frappe.validated = true;
- this.script_manager.trigger("before_cancel").then(() => {
+ me.script_manager.trigger("before_cancel").then(() => {
if (!frappe.validated) {
- return this.handle_save_fail(btn, on_error);
+ return me.handle_save_fail(btn, on_error);
}
- const original_name = this.docname;
- const after_cancel = (r) => {
+ var after_cancel = function(r) {
if (r.exc) {
- this.handle_save_fail(btn, on_error);
+ me.handle_save_fail(btn, on_error);
} else {
frappe.utils.play_sound("cancel");
+ me.refresh();
callback && callback();
- this.script_manager.trigger("after_cancel");
- frappe.run_serially([
- () => this.rename_notify(this.doctype, original_name, r.docs[0].name),
- () => frappe.router.clear_re_route(this.doctype, original_name),
- () => this.refresh(),
- ]);
+ me.script_manager.trigger("after_cancel");
}
};
- frappe.ui.form.save(this, "cancel", after_cancel, btn);
+ frappe.ui.form.save(me, "cancel", after_cancel, btn);
});
}
if (skip_confirm) {
cancel_doc();
} else {
- frappe.confirm(__("Permanently Cancel {0}?", [this.docname]), cancel_doc, this.handle_save_fail(btn, on_error));
+ frappe.confirm(__("Permanently Cancel {0}?", [this.docname]), cancel_doc, me.handle_save_fail(btn, on_error));
}
}
@@ -911,7 +907,7 @@ frappe.ui.form.Form = class FrappeForm {
'docname': this.doc.name
}).then(is_amended => {
if (is_amended) {
- frappe.throw(__('This document is already amended, you cannot amend it again'));
+ frappe.throw(__('This document is already amended, you cannot ammend it again'));
}
this.validate_form_action("Amend");
var me = this;
@@ -943,7 +939,10 @@ frappe.ui.form.Form = class FrappeForm {
// re-enable buttons
resolve();
}
- frappe.throw (__("No permission to '{0}' {1}", [__(action), __(this.doc.doctype)]));
+
+ frappe.throw(
+ __("No permission to '{0}' {1}", [__(action), __(this.doc.doctype)], "{0} = verb, {1} = object")
+ );
}
}
@@ -1146,8 +1145,7 @@ frappe.ui.form.Form = class FrappeForm {
subject: __(this.meta.name) + ': ' + this.docname,
recipients: this.doc.email || this.doc.email_id || this.doc.contact_email,
attach_document_print: true,
- message: message,
- real_name: this.doc.real_name || this.doc.contact_display || this.doc.contact_name
+ message: message
});
}
diff --git a/frappe/public/js/frappe/form/form_viewers.js b/frappe/public/js/frappe/form/form_viewers.js
index 964576ef8a..ecf5eea504 100644
--- a/frappe/public/js/frappe/form/form_viewers.js
+++ b/frappe/public/js/frappe/form/form_viewers.js
@@ -27,19 +27,40 @@ frappe.ui.form.FormViewers.set_users = function(data, type) {
const users = data.users || [];
const new_users = users.filter(user => !past_users.includes(user));
- frappe.model.set_docinfo(doctype, docname, type, {
- past: past_users.concat(new_users),
- new: new_users,
- current: users
- });
+ if (new_users.length===0) return;
- if (
- cur_frm &&
- cur_frm.doc &&
- cur_frm.doc.doctype === doctype &&
- cur_frm.doc.name == docname &&
- cur_frm.viewers
- ) {
- cur_frm.viewers.refresh(true, type);
+ const set_and_refresh = () => {
+ const info = {
+ past: past_users.concat(new_users),
+ new: new_users,
+ current: users
+ };
+
+ frappe.model.set_docinfo(doctype, docname, type, info);
+
+ if (
+ cur_frm &&
+ cur_frm.doc &&
+ cur_frm.doc.doctype === doctype &&
+ cur_frm.doc.name == docname &&
+ cur_frm.viewers
+ ) {
+ cur_frm.viewers.refresh(true, type);
+ }
+ };
+
+ let unknown_users = [];
+ for (let user of users) {
+ if (!frappe.boot.user_info[user]) unknown_users.push(user);
+ }
+
+ if (unknown_users.length===0) {
+ set_and_refresh();
+ } else {
+ // load additional user info
+ frappe.xcall('frappe.desk.form.load.get_user_info_for_viewers', {users: unknown_users}).then((data) => {
+ Object.assign(frappe.boot.user_info, data);
+ set_and_refresh();
+ });
}
};
diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js
index bc0286e62d..bc5f7a9b52 100644
--- a/frappe/public/js/frappe/form/multi_select_dialog.js
+++ b/frappe/public/js/frappe/form/multi_select_dialog.js
@@ -150,8 +150,12 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
});
}
+ is_child_selection_enabled() {
+ return this.dialog.fields_dict['allow_child_item_selection'].get_value();
+ }
+
toggle_child_selection() {
- if (this.dialog.fields_dict['allow_child_item_selection'].get_value()) {
+ if (this.is_child_selection_enabled()) {
this.show_child_results();
} else {
this.child_results = [];
@@ -289,7 +293,11 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
parent: this.dialog.get_field('filter_area').$wrapper,
doctype: this.doctype,
on_change: () => {
- this.get_results();
+ if (this.is_child_selection_enabled()) {
+ this.show_child_results();
+ } else {
+ this.get_results();
+ }
}
});
// 'Apply Filter' breaks since the filers are not in a popover
@@ -325,7 +333,11 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
this.$parent.find('.input-with-feedback').on('change', () => {
frappe.flags.auto_scroll = false;
- this.get_results();
+ if (this.is_child_selection_enabled()) {
+ this.show_child_results();
+ } else {
+ this.get_results();
+ }
});
this.$parent.find('[data-fieldtype="Data"]').on('input', () => {
@@ -333,8 +345,12 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
clearTimeout($this.data('timeout'));
$this.data('timeout', setTimeout(function () {
frappe.flags.auto_scroll = false;
- me.empty_list();
- me.get_results();
+ if (me.is_child_selection_enabled()) {
+ me.show_child_results();
+ } else {
+ me.empty_list();
+ me.get_results();
+ }
}, 300));
});
}
diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js
index 65d84e2202..934c90f017 100644
--- a/frappe/public/js/frappe/form/save.js
+++ b/frappe/public/js/frappe/form/save.js
@@ -7,12 +7,12 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
$(btn).prop("disabled", true);
// specified here because there are keyboard shortcuts to save
- var working_label = {
- "Save": __("Saving"),
- "Submit": __("Submitting"),
- "Update": __("Updating"),
- "Amend": __("Amending"),
- "Cancel": __("Cancelling")
+ const working_label = {
+ "Save": __("Saving", null, "Freeze message while saving a document"),
+ "Submit": __("Submitting", null, "Freeze message while submitting a document"),
+ "Update": __("Updating", null, "Freeze message while updating a document"),
+ "Amend": __("Amending", null, "Freeze message while amending a document"),
+ "Cancel": __("Cancelling", null, "Freeze message while cancelling a document"),
}[toTitle(action)];
var freeze_message = working_label ? __(working_label) : "";
@@ -154,8 +154,8 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
if (error_fields.length) {
let meta = frappe.get_meta(doc.doctype);
if (meta.istable) {
- var message = __('Mandatory fields required in table {0}, Row {1}',
- [__(frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label).bold(), doc.idx]);
+ const table_label = __(frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label).bold();
+ var message = __('Mandatory fields required in table {0}, Row {1}', [table_label, doc.idx]);
} else {
var message = __('Mandatory fields required in {0}', [__(doc.doctype)]);
}
@@ -276,4 +276,3 @@ frappe.ui.form.update_calling_link = (newdoc) => {
frappe._from_link = null;
}
}
-
diff --git a/frappe/public/js/frappe/form/script_manager.js b/frappe/public/js/frappe/form/script_manager.js
index d1732ee702..6169fa75b8 100644
--- a/frappe/public/js/frappe/form/script_manager.js
+++ b/frappe/public/js/frappe/form/script_manager.js
@@ -192,7 +192,7 @@ frappe.ui.form.ScriptManager = class ScriptManager {
}
function setup_add_fetch(df) {
- if ((['Data', 'Read Only', 'Text', 'Small Text', 'Currency', 'Check',
+ if ((['Data', 'Read Only', 'Text', 'Small Text', 'Currency', 'Check', 'Attach Image',
'Text Editor', 'Code', 'Link', 'Float', 'Int', 'Date', 'Select', 'Duration'].includes(df.fieldtype) || df.read_only==1)
&& df.fetch_from && df.fetch_from.indexOf(".")!=-1) {
var parts = df.fetch_from.split(".");
diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html
index efa8b9ea5e..dcea2f4647 100644
--- a/frappe/public/js/frappe/form/templates/form_sidebar.html
+++ b/frappe/public/js/frappe/form/templates/form_sidebar.html
@@ -1,5 +1,5 @@
-