diff --git a/frappe/core/doctype/server_script/server_script.js b/frappe/core/doctype/server_script/server_script.js index 86b99c1c12..736e9b5346 100644 --- a/frappe/core/doctype/server_script/server_script.js +++ b/frappe/core/doctype/server_script/server_script.js @@ -44,16 +44,15 @@ frappe.ui.form.on("Server Script", { }, setup_help(frm) { - frm.get_field("help_html").html(` + const help_field = frm.get_field("help_html"); + help_field.html(`
Add logic for standard doctype events like Before Insert, After Submit, etc.
-
-
+
# set property
if "test" in doc.description:
doc.status = 'Closed'
-
# validate
if "validate" in doc.description:
raise frappe.ValidationError
@@ -65,13 +64,11 @@ if doc.allocated_to:
owner = doc.allocated_to,
description = doc.subject
)).insert()
-
-
+
Payment processing events have a special state. See the PaymentController in Frappe Payments for details.
-
-
+
# retreive payment session state
ps = doc.flags.payment_session
@@ -81,7 +78,10 @@ if ps.is_success:
# custom process return values
doc.flags.payment_session.result = {
"message": "Thank you for your payment",
- "action": {"href": "https://shop.example.com", "label": "Return to shop"},
+ "action": {
+ "href": "https://shop.example.com",
+ "label": "Return to shop"
+ }
}
if ps.is_pre_authorized:
if ps.changed: # could be an idempotent run
@@ -92,21 +92,20 @@ if ps.is_processing:
if ps.is_declined:
if ps.changed: # could be an idempotent run
...
-
-
+
+
The On Payment Failed (on_payment_failed) event only transports the error message which the controller implementation had extracted from the transaction.
-
+
+
msg = doc.flags.payment_failure_message
doc.my_failure_message_field = msg
-
-
+
Respond to /api/method/<method-name> calls, just like whitelisted methods
+
# respond to API
if frappe.form_dict.message == "ping":
@@ -119,12 +118,16 @@ else:
Permission Query
Add conditions to the where clause of list queries.
-
-# generate dynamic conditions and set it in the conditions variable
-tenant_id = frappe.db.get_value(...)
-conditions = f'tenant_id = {tenant_id}'
+Generate dynamic conditions and set it in the conditions variable:
-# resulting select query
+
+tenant_id = frappe.db.get_value(...) # -> 2
+conditions = f'tenant_id = {tenant_id}'
+
+
+The resulting select query is:
+
+
select name from \`tabPerson\`
where tenant_id = 2
order by creation desc
@@ -135,15 +138,13 @@ order by creation desc
Workflow Task
Execute when a particular Workflow Action Master is executed.
Gets the document which the action is being applied on in the doc variable.
-
+
# create a customer with the same name as the given document
-
customer = frappe.new_doc("Customer")
-customer.customer_name = doc.first_name + " " + doc.last_name # we get this from the workflow action
+customer.customer_name = doc.first_name + " " + doc.last_name # we get this doc from the workflow action
customer.customer_type = "Company"
-
-c.save()
-
-`);
+customer.save()
+
`);
+ frappe.utils.highlight_pre(help_field.$wrapper);
},
});
diff --git a/frappe/custom/doctype/client_script/client_script.js b/frappe/custom/doctype/client_script/client_script.js
index 481aa3ffd7..a09e1e72b2 100644
--- a/frappe/custom/doctype/client_script/client_script.js
+++ b/frappe/custom/doctype/client_script/client_script.js
@@ -3,7 +3,9 @@
frappe.ui.form.on("Client Script", {
setup(frm) {
- frm.get_field("sample").html(SAMPLE_HTML);
+ const sample_field = frm.get_field("sample");
+ sample_field.html(SAMPLE_HTML);
+ frappe.utils.highlight_pre(sample_field.$wrapper);
},
refresh(frm) {
if (frm.doc.dt && frm.doc.script) {
@@ -106,53 +108,50 @@ frappe.ui.form.on('${doctype}', {
const SAMPLE_HTML = `Client Script Help
Client Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started
-
-
+
// fetch local_tax_no on selection of customer
-// cur_frm.add_fetch(link_field, source_fieldname, target_fieldname);
-cur_frm.add_fetch("customer", "local_tax_no', 'local_tax_no');
+// cur_frm.add_fetch(link_field, source_fieldname, target_fieldname);
+cur_frm.add_fetch("customer", "local_tax_no", "local_tax_no");
// additional validation on dates
-frappe.ui.form.on('Task', 'validate', function(frm) {
+frappe.ui.form.on("Task", "validate", function(frm) {
if (frm.doc.from_date < get_today()) {
- msgprint('You can not select past date in From Date');
+ msgprint("You can not select past date in From Date");
validated = false;
}
});
// make a field read-only after saving
-frappe.ui.form.on('Task', {
+frappe.ui.form.on("Task", {
refresh: function(frm) {
- // use the __islocal value of doc, to check if the doc is saved or not
- frm.set_df_property('myfield', 'read_only', frm.doc.__islocal ? 0 : 1);
+ frm.set_df_property("myfield", "read_only", frm.is_new() ? 0 : 1);
}
});
// additional permission check
-frappe.ui.form.on('Task', {
+frappe.ui.form.on("Task", {
validate: function(frm) {
- if(user=='user1@example.com' && frm.doc.purpose!='Material Receipt') {
- msgprint('You are only allowed Material Receipt');
+ if(user === "user1@example.com" && frm.doc.purpose !== "Material Receipt") {
+ msgprint("You are only allowed Material Receipt");
validated = false;
}
}
});
// calculate sales incentive
-frappe.ui.form.on('Sales Invoice', {
+frappe.ui.form.on("Sales Invoice", {
validate: function(frm) {
// calculate incentives for each person on the deal
total_incentive = 0
- $.each(frm.doc.sales_team, function(i, d) {
+ for (const row of frm.doc.sales_team) {
// calculate incentive
var incentive_percent = 2;
if(frm.doc.base_grand_total > 400) incentive_percent = 4;
// actual incentive
- d.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;
- total_incentive += flt(d.incentives)
+ row.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;
+ total_incentive += flt(row.incentives)
});
frm.doc.total_incentive = total_incentive;
}
})
-
`;
diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js
index 9f430e9143..518b06daaa 100644
--- a/frappe/email/doctype/notification/notification.js
+++ b/frappe/email/doctype/notification/notification.js
@@ -112,7 +112,7 @@ frappe.notification = {
if (frm.doc.channel === "Email") {
template = `Message Example
-<h3>Order Overdue</h3>
+<h3>Order Overdue</h3>
<p>Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.</p>
@@ -127,7 +127,7 @@ Last comment: {{ comments[-1].comment }} by {{ comments[-1].by }}
<li>Customer: {{ doc.customer }}</li>
<li>Amount: {{ doc.grand_total }}</li>
</ul>
-
+
`;
} else if (["Slack", "System Notification", "SMS"].includes(frm.doc.channel)) {
template = `Message Example
@@ -148,7 +148,11 @@ Last comment: {{ comments[-1].comment }} by {{ comments[-1].by }}
`;
}
if (template) {
- frm.set_df_property("message_examples", "options", template);
+ const message_examples_field = frm.get_field("message_examples");
+ message_examples_field.html(template);
+ if (frm.doc.channel === "Email") {
+ frappe.utils.highlight_pre(message_examples_field.$wrapper);
+ }
}
},
};
diff --git a/frappe/public/js/frappe/form/controls/base_input.js b/frappe/public/js/frappe/form/controls/base_input.js
index 152a04ac0a..f6d63b4120 100644
--- a/frappe/public/js/frappe/form/controls/base_input.js
+++ b/frappe/public/js/frappe/form/controls/base_input.js
@@ -206,7 +206,17 @@ frappe.ui.form.ControlInput = class ControlInput extends frappe.ui.form.Control
return;
}
if (this.df.description) {
- this.$wrapper.find(".help-box").html(__(this.df.description, null, this.df.parent));
+ const description = __(this.df.description, null, this.df.parent);
+ const help_box = this.$wrapper.find(".help-box");
+ help_box.html(description);
+ if (description.includes(" {
+ help_box.find("code").each(function () {
+ hljs.highlightElement(this);
+ this.style.display = "inline"; // override hljs's "block" display
+ });
+ });
+ }
this.toggle_description(true);
} else {
this.set_empty_description();
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index 37ff0925f0..4c0787bdff 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -1837,4 +1837,29 @@ Object.assign(frappe.utils, {
}
}
},
+
+ /**
+ * Adds syntax highlighting to all tags in the given jQuery wrapper.
+ * Example wrapper:
+ *
+ * ```html
+ *
+ * def add(a, b):
+ * return a + b
+ *
+ * print(add(1, 2))
+ *
+ * # Output: 3
+ *
+ * ```
+ *
+ * @param {jQuery} $wrapper - The jQuery wrapper to add syntax highlighting to.
+ */
+ highlight_pre($wrapper) {
+ frappe.require("syntax_highlighting.bundle.js").then(() => {
+ $wrapper.find("pre").each(function () {
+ hljs.highlightElement(this);
+ });
+ });
+ },
});
diff --git a/frappe/public/js/syntax_highlighting.bundle.js b/frappe/public/js/syntax_highlighting.bundle.js
new file mode 100644
index 0000000000..d0b6326b9d
--- /dev/null
+++ b/frappe/public/js/syntax_highlighting.bundle.js
@@ -0,0 +1,12 @@
+import hljs from "highlight.js/lib/core";
+import javascript from "highlight.js/lib/languages/javascript";
+import python from "highlight.js/lib/languages/python";
+import xml from "highlight.js/lib/languages/xml";
+import sql from "highlight.js/lib/languages/sql";
+
+hljs.registerLanguage("javascript", javascript);
+hljs.registerLanguage("python", python);
+hljs.registerLanguage("xml", xml);
+hljs.registerLanguage("sql", sql);
+
+window.hljs = hljs;
diff --git a/frappe/public/scss/desk.bundle.scss b/frappe/public/scss/desk.bundle.scss
index cbf365861a..6b673619c0 100644
--- a/frappe/public/scss/desk.bundle.scss
+++ b/frappe/public/scss/desk.bundle.scss
@@ -9,3 +9,4 @@
@import "frappe/public/js/lib/leaflet_easy_button/easy-button.css";
@import "frappe/public/js/lib/leaflet_control_locate/L.Control.Locate.css";
@import "frappe/public/js/lib/leaflet_draw/leaflet.draw.css";
+@import "frappe/public/node_modules/highlight.js/styles/tomorrow.css";