From 68829ab2e6d2ca8238bd9d5bd44844e032cee089 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 21 Jan 2021 19:06:33 +0100 Subject: [PATCH 01/53] feat: default_email_template --- frappe/core/doctype/doctype/doctype.json | 13 ++++++++++++- frappe/public/js/frappe/views/communication.js | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 215ef8cd62..55c74a4e63 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -55,6 +55,8 @@ "show_preview_popup", "show_name_in_global_search", "email_settings_sb", + "default_email_template", + "column_break_51", "email_append_to", "sender_field", "subject_field", @@ -528,6 +530,15 @@ "fieldname": "index_web_pages_for_search", "fieldtype": "Check", "label": "Index Web Pages for Search" + }, + { + "fieldname": "default_email_template", + "fieldtype": "Data", + "label": "Default Email Template" + }, + { + "fieldname": "column_break_51", + "fieldtype": "Column Break" } ], "icon": "fa fa-bolt", @@ -609,7 +620,7 @@ "link_fieldname": "reference_doctype" } ], - "modified": "2020-09-24 13:13:58.227153", + "modified": "2021-01-21 18:09:47.135696", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index c69be04347..7b9668a96e 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -159,6 +159,7 @@ frappe.views.CommunicationComposer = Class.extend({ this.setup_last_edited_communication(); this.setup_email_template(); + this.dialog.set_value("email_template", this.frm.meta.default_email_template || ''); this.dialog.set_value("recipients", this.recipients || ''); this.dialog.set_value("cc", this.cc || ''); this.dialog.set_value("bcc", this.bcc || ''); From 2618ee74d898f48a0899691c045e2ce1e0a4b4da Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 27 Jan 2021 18:55:54 +0100 Subject: [PATCH 02/53] feat: add default_email_template to Customize Form --- .../doctype/customize_form/customize_form.json | 13 ++++++++++++- .../custom/doctype/customize_form/customize_form.py | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index ff102b3c08..dee2dfce0d 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -31,6 +31,8 @@ "show_preview_popup", "image_view", "email_settings_section", + "default_email_template", + "column_break_26", "email_append_to", "sender_field", "subject_field", @@ -261,6 +263,15 @@ "fieldtype": "Table", "label": "Actions", "options": "DocType Action" + }, + { + "fieldname": "default_email_template", + "fieldtype": "Data", + "label": "Default Email Template" + }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" } ], "hide_toolbar": 1, @@ -269,7 +280,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-09-24 14:16:49.594012", + "modified": "2021-01-27 18:26:59.705786", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 82513783c7..0718f5d84c 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -479,6 +479,7 @@ doctype_properties = { 'allow_auto_repeat': 'Check', 'allow_import': 'Check', 'show_preview_popup': 'Check', + 'default_email_template': 'Data', 'email_append_to': 'Check', 'subject_field': 'Data', 'sender_field': 'Data' From b14b28d7650001af5d7812bda07c15cb74e883ff Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 27 Jan 2021 18:59:00 +0100 Subject: [PATCH 03/53] fix: check if frm is available Prevents error when creating new Communication from list view. --- frappe/public/js/frappe/views/communication.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 7b9668a96e..073ce44206 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -159,7 +159,10 @@ frappe.views.CommunicationComposer = Class.extend({ this.setup_last_edited_communication(); this.setup_email_template(); - this.dialog.set_value("email_template", this.frm.meta.default_email_template || ''); + if ('frm' in this) { + this.dialog.set_value("email_template", this.frm.meta.default_email_template || ''); + } + this.dialog.set_value("recipients", this.recipients || ''); this.dialog.set_value("cc", this.cc || ''); this.dialog.set_value("bcc", this.bcc || ''); From fa39484571d993ed5562149b6c85615e39cdc27b Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 27 Jan 2021 18:59:47 +0100 Subject: [PATCH 04/53] fix: check if email_template is set Prevents error on empty email_template. --- frappe/public/js/frappe/views/communication.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 073ce44206..c5c6cdced9 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -232,6 +232,9 @@ frappe.views.CommunicationComposer = Class.extend({ 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) { From 885d198622703ef50b0f6443f3a00ee76b0c2d58 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Feb 2021 12:20:12 +0100 Subject: [PATCH 05/53] fix: don't apply default email template for reply --- frappe/public/js/frappe/views/communication.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index f1feb4f6a3..66e050f3d1 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -196,10 +196,6 @@ frappe.views.CommunicationComposer = Class.extend({ this.setup_last_edited_communication(); this.setup_email_template(); - if ('frm' in this) { - this.dialog.set_value("email_template", this.frm.meta.default_email_template || ''); - } - this.dialog.set_value("recipients", this.recipients || ''); this.dialog.set_value("cc", this.cc || ''); this.dialog.set_value("bcc", this.bcc || ''); @@ -210,6 +206,11 @@ frappe.views.CommunicationComposer = Class.extend({ this.dialog.fields_dict.subject.set_value(this.subject || ''); this.setup_earlier_reply(); + + 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 || ''); + } }, setup_subject_and_recipients: function() { From cb211c6272669662d1012aa7331538ba856c1579 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 8 Feb 2021 12:51:47 +0100 Subject: [PATCH 06/53] fix: signature should be an empty string by default (would become undefined if the server message was empty) --- frappe/public/js/frappe/views/communication.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 66e050f3d1..5329967e12 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -726,7 +726,7 @@ frappe.views.CommunicationComposer = Class.extend({ if (!signature) { const res = await this.get_default_outgoing_email_account_signature(); - signature = res.message.signature; + signature = res.message.signature || ""; } if(!frappe.utils.is_html(signature)) { From 87326625fed3f0ce0b72977f2e988783e59741c8 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 22 Mar 2021 12:31:58 +0100 Subject: [PATCH 07/53] fix: make Default Email Template a link field --- frappe/core/doctype/doctype/doctype.json | 7 ++++--- frappe/custom/doctype/customize_form/customize_form.json | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 7bf91892ee..fb24c095f3 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -533,8 +533,9 @@ }, { "fieldname": "default_email_template", - "fieldtype": "Data", - "label": "Default Email Template" + "fieldtype": "Link", + "label": "Default Email Template", + "options": "Email Template" }, { "fieldname": "column_break_51", @@ -620,7 +621,7 @@ "link_fieldname": "reference_doctype" } ], - "modified": "2021-02-23 15:10:09.227205", + "modified": "2021-03-22 12:26:41.031135", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index dee2dfce0d..f8db73137e 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -266,8 +266,9 @@ }, { "fieldname": "default_email_template", - "fieldtype": "Data", - "label": "Default Email Template" + "fieldtype": "Link", + "label": "Default Email Template", + "options": "Email Template" }, { "fieldname": "column_break_26", @@ -280,7 +281,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-01-27 18:26:59.705786", + "modified": "2021-03-22 12:27:15.462727", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", From 5290b4c65f15642c5bc7c84d70fcfc4c14c3d883 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sat, 27 Mar 2021 15:01:37 +0100 Subject: [PATCH 08/53] fix: add back column break that was lost in merge --- frappe/custom/doctype/customize_form/customize_form.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index fc74f0881b..442b8dbb31 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -272,6 +272,10 @@ "label": "Default Email Template", "options": "Email Template" }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, { "collapsible": 1, "fieldname": "naming_section", @@ -312,4 +316,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 15b3cc890f9c14c78b2fb6932e698ce206c54c95 Mon Sep 17 00:00:00 2001 From: casesolved-co-uk Date: Sat, 6 Mar 2021 08:55:58 +0000 Subject: [PATCH 09/53] feat: allow query/custom reports to save custom data in the json field --- .../prepared_report/prepared_report.py | 5 ++++- frappe/desk/query_report.py | 14 +++++++++---- frappe/patches.txt | 1 + frappe/patches/v13_0/queryreport_columns.py | 21 +++++++++++++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 frappe/patches/v13_0/queryreport_columns.py diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index e947cee8ed..c27853f460 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -37,7 +37,10 @@ def run_background(prepared_report): custom_report_doc = report reference_report = custom_report_doc.reference_report report = frappe.get_doc("Report", reference_report) - report.custom_columns = custom_report_doc.json + if custom_report_doc.json: + data = json.loads(custom_report_doc.json) + if data: + report.custom_columns = data["columns"] result = generate_report_result( report=report, diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 22d47d1120..9589507ca6 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -36,7 +36,10 @@ def get_report_doc(report_name): reference_report = custom_report_doc.reference_report doc = frappe.get_doc("Report", reference_report) doc.custom_report = report_name - doc.custom_columns = custom_report_doc.json + if custom_report_doc.json: + data = json.loads(custom_report_doc.json) + if data: + doc.custom_columns = data["columns"] doc.is_custom_report = True if not doc.is_permitted(): @@ -83,7 +86,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) if report.custom_columns: # saved columns (with custom columns / with different column order) - columns = json.loads(report.custom_columns) + columns = report.custom_columns # unsaved custom_columns if custom_columns: @@ -524,9 +527,12 @@ def save_report(reference_report, report_name, columns): "report_type": "Custom Report", }, ) + if docname: report = frappe.get_doc("Report", docname) - report.update({"json": columns}) + existing_jd = json.loads(report.json) + existing_jd["columns"] = json.loads(columns) + report.update({"json": json.dumps(existing_jd, separators=(',', ':'))}) report.save() frappe.msgprint(_("Report updated successfully")) @@ -536,7 +542,7 @@ def save_report(reference_report, report_name, columns): { "doctype": "Report", "report_name": report_name, - "json": columns, + "json": f'{{"columns":{columns}}}', "ref_doctype": report_doc.ref_doctype, "is_standard": "No", "report_type": "Custom Report", diff --git a/frappe/patches.txt b/frappe/patches.txt index 5251b3da30..516ddb6094 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -334,3 +334,4 @@ frappe.patches.v13_0.delete_package_publish_tool frappe.patches.v13_0.rename_list_view_setting_to_list_view_settings frappe.patches.v13_0.remove_twilio_settings frappe.patches.v12_0.rename_uploaded_files_with_proper_name +frappe.patches.v13_0.queryreport_columns diff --git a/frappe/patches/v13_0/queryreport_columns.py b/frappe/patches/v13_0/queryreport_columns.py new file mode 100644 index 0000000000..cc09bae37c --- /dev/null +++ b/frappe/patches/v13_0/queryreport_columns.py @@ -0,0 +1,21 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe, json + +def execute(): + """Convert Query Report json to support other content""" + records = frappe.get_all('Report', + filters={ + "json": ["!=", ""] + }, + fields=["name", "json"] + ) + for record in records: + jstr = record["json"] + data = json.loads(jstr) + if isinstance(data, list): + # double escape braces + jstr = f'{{"columns":{jstr}}}' + frappe.db.update('Report', record["name"], "json", jstr) From 49a79e4d0076a940a32763191dcfa45e846dd014 Mon Sep 17 00:00:00 2001 From: casesolved-co-uk Date: Fri, 9 Apr 2021 00:27:02 +0000 Subject: [PATCH 10/53] chore: fix sider --- frappe/patches/v13_0/queryreport_columns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/patches/v13_0/queryreport_columns.py b/frappe/patches/v13_0/queryreport_columns.py index cc09bae37c..6c2a1b1219 100644 --- a/frappe/patches/v13_0/queryreport_columns.py +++ b/frappe/patches/v13_0/queryreport_columns.py @@ -2,7 +2,8 @@ # MIT License. See license.txt from __future__ import unicode_literals -import frappe, json +import frappe +import json def execute(): """Convert Query Report json to support other content""" From 4c787effc67f9c4912599a0b4e028c7e57de4102 Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 11 Apr 2021 01:52:43 +0530 Subject: [PATCH 11/53] fix: don't allow user to remove/change data source file in data import --- frappe/core/doctype/data_import/data_import.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/data_import/data_import.json b/frappe/core/doctype/data_import/data_import.json index 8b1b6c4e07..fe6fb90481 100644 --- a/frappe/core/doctype/data_import/data_import.json +++ b/frappe/core/doctype/data_import/data_import.json @@ -53,7 +53,8 @@ "fieldname": "import_file", "fieldtype": "Attach", "in_list_view": 1, - "label": "Import File" + "label": "Import File", + "read_only_depends_on": "eval: ['Success', 'Partial Success'].includes(doc.status)" }, { "fieldname": "import_preview", @@ -156,10 +157,11 @@ "description": "Must be a publicly accessible Google Sheets URL", "fieldname": "google_sheets_url", "fieldtype": "Data", - "label": "Import from Google Sheets" + "label": "Import from Google Sheets", + "read_only_depends_on": "eval: ['Success', 'Partial Success'].includes(doc.status)" }, { - "depends_on": "eval:doc.google_sheets_url && !doc.__unsaved", + "depends_on": "eval:doc.google_sheets_url && !doc.__unsaved && ['Success', 'Partial Success'].includes(doc.status)", "fieldname": "refresh_google_sheet", "fieldtype": "Button", "label": "Refresh Google Sheet" @@ -167,7 +169,7 @@ ], "hide_toolbar": 1, "links": [], - "modified": "2020-06-24 14:33:03.173876", + "modified": "2021-04-11 01:50:42.074623", "modified_by": "Administrator", "module": "Core", "name": "Data Import", From 1a075a97ac433b9f51a17598e4c13fb556a12229 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 9 Apr 2021 21:36:31 +0530 Subject: [PATCH 12/53] chore: static analysis hack for globals --- frappe/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frappe/__init__.py b/frappe/__init__.py index 785d5ee7e5..bc2c933f5a 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -15,6 +15,7 @@ from __future__ import unicode_literals, print_function from six import iteritems, binary_type, text_type, string_types, PY2 from werkzeug.local import Local, release_local import os, sys, importlib, inspect, json +import typing from past.builtins import cmp import click @@ -133,6 +134,14 @@ message_log = local("message_log") lang = local("lang") +# This if block is never executed when running the code. It is only used for +# telling static code analyzer where to find dynamically defined attributes. +if typing.TYPE_CHECKING: + from frappe.database.mariadb.database import MariaDBDatabase + from frappe.database.postgres.database import PostgresDatabase + db: typing.Union[MariaDBDatabase, PostgresDatabase] +# end: static analysis hack + def init(site, sites_path=None, new_site=False): """Initialize frappe for the current site. Reset thread locals `frappe.local`""" if getattr(local, "initialised", None): From a1190a0c4a9e2f1943d30a3206420367a451340b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 12 Apr 2021 16:35:11 +0530 Subject: [PATCH 13/53] feat: Add option to mention a group of users --- frappe/boot.py | 2 + frappe/core/doctype/user/user.py | 5 ++ frappe/core/doctype/user_group/__init__.py | 0 .../doctype/user_group/test_user_group.py | 10 ++++ frappe/core/doctype/user_group/user_group.js | 8 +++ .../core/doctype/user_group/user_group.json | 50 +++++++++++++++++++ frappe/core/doctype/user_group/user_group.py | 10 ++++ .../doctype/user_group_member/__init__.py | 0 .../test_user_group_member.py | 10 ++++ .../user_group_member/user_group_member.js | 8 +++ .../user_group_member/user_group_member.json | 32 ++++++++++++ .../user_group_member/user_group_member.py | 10 ++++ .../controls/quill-mention/blots/mention.js | 2 + .../controls/quill-mention/quill.mention.js | 2 + frappe/public/js/frappe/utils/utils.js | 9 ++++ frappe/public/scss/common/quill.scss | 6 ++- frappe/public/scss/desk/timeline.scss | 3 +- frappe/utils/html_utils.py | 2 +- 18 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 frappe/core/doctype/user_group/__init__.py create mode 100644 frappe/core/doctype/user_group/test_user_group.py create mode 100644 frappe/core/doctype/user_group/user_group.js create mode 100644 frappe/core/doctype/user_group/user_group.json create mode 100644 frappe/core/doctype/user_group/user_group.py create mode 100644 frappe/core/doctype/user_group_member/__init__.py create mode 100644 frappe/core/doctype/user_group_member/test_user_group_member.py create mode 100644 frappe/core/doctype/user_group_member/user_group_member.js create mode 100644 frappe/core/doctype/user_group_member/user_group_member.json create mode 100644 frappe/core/doctype/user_group_member/user_group_member.py diff --git a/frappe/boot.py b/frappe/boot.py index 0dfcb8d1b4..65a07b15e5 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -42,6 +42,8 @@ def get_bootinfo(): bootinfo.user_info = get_user_info() bootinfo.sid = frappe.session['sid'] + bootinfo.user_groups = frappe.get_all('User Group', pluck="name") + bootinfo.modules = {} bootinfo.module_list = [] load_desktop_data(bootinfo) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 04d087e82a..035333cbe9 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1018,8 +1018,13 @@ def extract_mentions(txt): soup = BeautifulSoup(txt, 'html.parser') emails = [] for mention in soup.find_all(class_='mention'): + if mention.get('data-is-group'): + user_group = frappe.get_cached_doc('User Group', mention['data-id']) + emails += [d.user for d in user_group.user_group_members] + continue email = mention['data-id'] emails.append(email) + return emails def handle_password_test_fail(result): diff --git a/frappe/core/doctype/user_group/__init__.py b/frappe/core/doctype/user_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/user_group/test_user_group.py b/frappe/core/doctype/user_group/test_user_group.py new file mode 100644 index 0000000000..c7e28f3d31 --- /dev/null +++ b/frappe/core/doctype/user_group/test_user_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestUserGroup(unittest.TestCase): + pass diff --git a/frappe/core/doctype/user_group/user_group.js b/frappe/core/doctype/user_group/user_group.js new file mode 100644 index 0000000000..2aa9b68658 --- /dev/null +++ b/frappe/core/doctype/user_group/user_group.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('User Group', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/user_group/user_group.json b/frappe/core/doctype/user_group/user_group.json new file mode 100644 index 0000000000..3d859dcc9a --- /dev/null +++ b/frappe/core/doctype/user_group/user_group.json @@ -0,0 +1,50 @@ +{ + "actions": [], + "autoname": "field:group_name", + "creation": "2021-04-12 15:17:24.751710", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "group_name", + "user_group_members" + ], + "fields": [ + { + "fieldname": "group_name", + "fieldtype": "Data", + "label": "Group Name", + "unique": 1 + }, + { + "fieldname": "user_group_members", + "fieldtype": "Table MultiSelect", + "label": "User Group Members", + "options": "User Group Member" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-04-12 15:17:24.751710", + "modified_by": "Administrator", + "module": "Core", + "name": "User Group", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/user_group/user_group.py b/frappe/core/doctype/user_group/user_group.py new file mode 100644 index 0000000000..6fc6db4620 --- /dev/null +++ b/frappe/core/doctype/user_group/user_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class UserGroup(Document): + pass diff --git a/frappe/core/doctype/user_group_member/__init__.py b/frappe/core/doctype/user_group_member/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/user_group_member/test_user_group_member.py b/frappe/core/doctype/user_group_member/test_user_group_member.py new file mode 100644 index 0000000000..38aade4608 --- /dev/null +++ b/frappe/core/doctype/user_group_member/test_user_group_member.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestUserGroupMember(unittest.TestCase): + pass diff --git a/frappe/core/doctype/user_group_member/user_group_member.js b/frappe/core/doctype/user_group_member/user_group_member.js new file mode 100644 index 0000000000..0b2dbe0d46 --- /dev/null +++ b/frappe/core/doctype/user_group_member/user_group_member.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('User Group Member', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/user_group_member/user_group_member.json b/frappe/core/doctype/user_group_member/user_group_member.json new file mode 100644 index 0000000000..d2ff149366 --- /dev/null +++ b/frappe/core/doctype/user_group_member/user_group_member.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2021-04-12 15:16:29.279107", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-12 15:17:18.773046", + "modified_by": "Administrator", + "module": "Core", + "name": "User Group Member", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/user_group_member/user_group_member.py b/frappe/core/doctype/user_group_member/user_group_member.py new file mode 100644 index 0000000000..4d0656913d --- /dev/null +++ b/frappe/core/doctype/user_group_member/user_group_member.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class UserGroupMember(Document): + pass diff --git a/frappe/public/js/frappe/form/controls/quill-mention/blots/mention.js b/frappe/public/js/frappe/form/controls/quill-mention/blots/mention.js index 1c5787f854..d6907158f9 100644 --- a/frappe/public/js/frappe/form/controls/quill-mention/blots/mention.js +++ b/frappe/public/js/frappe/form/controls/quill-mention/blots/mention.js @@ -15,6 +15,7 @@ class MentionBlot extends Embed { node.dataset.id = data.id; node.dataset.value = data.value; node.dataset.denotationChar = data.denotationChar; + node.dataset.isGroup = data.isGroup; if (data.link) { node.dataset.link = data.link; } @@ -27,6 +28,7 @@ class MentionBlot extends Embed { value: domNode.dataset.value, link: domNode.dataset.link || null, denotationChar: domNode.dataset.denotationChar, + isGroup: domNode.dataset.isGroup, }; } } diff --git a/frappe/public/js/frappe/form/controls/quill-mention/quill.mention.js b/frappe/public/js/frappe/form/controls/quill-mention/quill.mention.js index ac1b9697f0..4b5326271e 100644 --- a/frappe/public/js/frappe/form/controls/quill-mention/quill.mention.js +++ b/frappe/public/js/frappe/form/controls/quill-mention/quill.mention.js @@ -149,6 +149,7 @@ class Mention { this.mentionList.childNodes[this.itemIndex].dataset.value, link: itemLink || null, denotationChar: this.mentionList.childNodes[this.itemIndex].dataset.denotationChar, + isGroup: this.mentionList.childNodes[this.itemIndex].dataset.isGroup, }; } @@ -197,6 +198,7 @@ class Mention { li.dataset.index = i; li.dataset.id = data[i].id; li.dataset.value = data[i].value; + li.dataset.isGroup = Boolean(data[i].is_group); li.dataset.denotationChar = mentionChar; if (data[i].link) { li.dataset.link = data[i].link; diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 302ebceeda..e1e4f437cd 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1285,6 +1285,15 @@ Object.assign(frappe.utils, { value: frappe.boot.user_info[user].fullname, }; }); + + frappe.boot.user_groups && frappe.boot.user_groups.map(group => { + names_for_mentions.push({ + id: group, + value: group, + is_group: true + }); + }); + return names_for_mentions; }, print(doctype, docname, print_format, letterhead, lang_code) { diff --git a/frappe/public/scss/common/quill.scss b/frappe/public/scss/common/quill.scss index e5303be5cf..c4011e2d6b 100644 --- a/frappe/public/scss/common/quill.scss +++ b/frappe/public/scss/common/quill.scss @@ -190,4 +190,8 @@ .mention>span { margin: 0 3px; -} \ No newline at end of file +} + +.mention[data-is-group="true"] { + background-color: var(--purple-100) !important; +} diff --git a/frappe/public/scss/desk/timeline.scss b/frappe/public/scss/desk/timeline.scss index 4bb3cbec78..a7e5d3dd9c 100644 --- a/frappe/public/scss/desk/timeline.scss +++ b/frappe/public/scss/desk/timeline.scss @@ -77,6 +77,7 @@ $threshold: 34; } } .document-email-link-container { + @extend .ellipsis; position: relative; padding: var(--padding-sm); font-size: var(--text-sm); @@ -141,4 +142,4 @@ $threshold: 34; --icon-stroke: var(--text-color); } } -} \ No newline at end of file +} diff --git a/frappe/utils/html_utils.py b/frappe/utils/html_utils.py index 81e5f2de3e..f3f86dcad2 100644 --- a/frappe/utils/html_utils.py +++ b/frappe/utils/html_utils.py @@ -177,7 +177,7 @@ acceptable_attributes = [ 'data-value', 'role', 'frameborder', 'allowfullscreen', 'spellcheck', 'data-mode', 'data-gramm', 'data-placeholder', 'data-comment', 'data-id', 'data-denotation-char', 'itemprop', 'itemscope', - 'itemtype', 'itemid', 'itemref', 'datetime' + 'itemtype', 'itemid', 'itemref', 'datetime', 'data-is-group' ] mathml_attributes = [ From 322a9f7623031bd2167cf00fe1fe9053815f05d6 Mon Sep 17 00:00:00 2001 From: "hasnain2808@gmail.com" Date: Mon, 12 Apr 2021 17:28:27 +0530 Subject: [PATCH 14/53] fix: none type error for empty lines --- frappe/email/doctype/auto_email_report/auto_email_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index d82caa7bd4..6f1cd8eebd 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -252,7 +252,7 @@ def make_links(columns, data): elif col.fieldtype == "Dynamic Link": if col.options and row.get(col.fieldname) and row.get(col.options): row[col.fieldname] = get_link_to_form(row[col.options], row[col.fieldname]) - elif col.fieldtype == "Currency": + elif col.fieldtype == "Currency" and row.get(col.fieldname): row[col.fieldname] = frappe.format_value(row[col.fieldname], col) return columns, data From 1f70959fa1d36ad7f008e13459b23a72ea6302b0 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Wed, 14 Apr 2021 05:39:31 +0530 Subject: [PATCH 15/53] feat: Add Enable/Disable Webhook via Check - Added a new field to Webhook DocType - Modfied the webhook run logic to only fetch enabled webhooks --- frappe/integrations/doctype/webhook/__init__.py | 4 +++- frappe/integrations/doctype/webhook/webhook.json | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/webhook/__init__.py b/frappe/integrations/doctype/webhook/__init__.py index 8b08db5f68..0524cd609b 100644 --- a/frappe/integrations/doctype/webhook/__init__.py +++ b/frappe/integrations/doctype/webhook/__init__.py @@ -21,7 +21,9 @@ def run_webhooks(doc, method): if webhooks is None: # query webhooks webhooks_list = frappe.get_all('Webhook', - fields=["name", "`condition`", "webhook_docevent", "webhook_doctype"]) + fields=["name", "`condition`", "webhook_docevent", "webhook_doctype"], + filters={"enabled": "1"} + ) # make webhooks map for cache webhooks = {} diff --git a/frappe/integrations/doctype/webhook/webhook.json b/frappe/integrations/doctype/webhook/webhook.json index 9f979099c9..85895c052c 100644 --- a/frappe/integrations/doctype/webhook/webhook.json +++ b/frappe/integrations/doctype/webhook/webhook.json @@ -11,6 +11,7 @@ "webhook_doctype", "cb_doc_events", "webhook_docevent", + "enabled", "sb_condition", "condition", "cb_condition", @@ -147,10 +148,16 @@ "fieldname": "webhook_secret", "fieldtype": "Password", "label": "Webhook Secret" + }, + { + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" } ], "links": [], - "modified": "2020-01-13 01:53:04.459968", + "modified": "2021-04-14 05:35:28.532049", "modified_by": "Administrator", "module": "Integrations", "name": "Webhook", From a373c00abd1db85a4a8304fecc299890c74e163e Mon Sep 17 00:00:00 2001 From: thebachy1 Date: Wed, 14 Apr 2021 12:39:46 -0500 Subject: [PATCH 16/53] fix: Load server translations in boot --- frappe/translate.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frappe/translate.py b/frappe/translate.py index cdcaa31920..62ee733f5f 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -109,6 +109,13 @@ def get_dict(fortype, name=None): elif fortype=="jsfile": messages = get_messages_from_file(name) elif fortype=="boot": + messages = [] + apps = frappe.get_all_apps(True) + for app in apps: + messages.extend(get_server_messages(app)) + messages = deduplicate_messages(messages) + + messages += frappe.db.sql("""select "navbar", item_label from `tabNavbar Item` where item_label is not null""") messages = get_messages_from_include_files() messages += frappe.db.sql("select 'Print Format:', name from `tabPrint Format`") messages += frappe.db.sql("select 'DocType:', name from tabDocType") From 2133777c4449532263f824c39c2d57452bf63b06 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Thu, 15 Apr 2021 07:46:39 +0530 Subject: [PATCH 17/53] test: Execute enabled webhook trigger --- .../integrations/doctype/webhook/__init__.py | 2 +- .../doctype/webhook/test_webhook.py | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/webhook/__init__.py b/frappe/integrations/doctype/webhook/__init__.py index 0524cd609b..19233bd175 100644 --- a/frappe/integrations/doctype/webhook/__init__.py +++ b/frappe/integrations/doctype/webhook/__init__.py @@ -22,7 +22,7 @@ def run_webhooks(doc, method): # query webhooks webhooks_list = frappe.get_all('Webhook', fields=["name", "`condition`", "webhook_docevent", "webhook_doctype"], - filters={"enabled": "1"} + filters={"enabled": True} ) # make webhooks map for cache diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py index fa7f9534e1..6c6e4c1a4d 100644 --- a/frappe/integrations/doctype/webhook/test_webhook.py +++ b/frappe/integrations/doctype/webhook/test_webhook.py @@ -10,6 +10,44 @@ from frappe.integrations.doctype.webhook.webhook import get_webhook_headers, get class TestWebhook(unittest.TestCase): + @classmethod + def setUpClass(cls): + # delete any existing webhooks + frappe.db.sql("DELETE FROM tabWebhook") + # create test webhooks + cls.create_sample_webhooks() + + @classmethod + def create_sample_webhooks(cls): + samples_webhooks_data = [ + { + "webhook_doctype": "User", + "webhook_docevent": "after_insert", + "request_url": "https://httpbin.org/post", + "condition": "doc.email", + "enabled": True + }, + { + "webhook_doctype": "User", + "webhook_docevent": "after_insert", + "request_url": "https://httpbin.org/post", + "condition": "doc.first_name", + "enabled": False + } + ] + + cls.sample_webhooks = [] + for wh_fields in samples_webhooks_data: + wh = frappe.new_doc("Webhook") + wh.update(wh_fields) + wh.insert() + cls.sample_webhooks.append(wh) + + @classmethod + def tearDownClass(cls): + # delete any existing webhooks + frappe.db.sql("DELETE FROM tabWebhook") + def setUp(self): # retrieve or create a User webhook for `after_insert` webhook_fields = { @@ -30,10 +68,37 @@ class TestWebhook(unittest.TestCase): self.user.email = frappe.mock("email") self.user.save() + # Create another test user specific to this test + self.test_user = frappe.new_doc("User") + self.test_user.email = "user1@integration.webhooks.test.com" + self.test_user.first_name = "user1" + def tearDown(self) -> None: self.user.delete() + self.test_user.delete() super().tearDown() + def test_webhook_trigger_with_enabled_webhooks(self): + """Test webhook trigger for enabled webhooks""" + + frappe.cache().delete_value('webhooks') + frappe.flags.webhooks = None + + # Insert the user to db + self.test_user.insert() + + self.assertTrue("User" in frappe.flags.webhooks) + # only 1 hook (enabled) must be queued + self.assertEqual( + len(frappe.flags.webhooks.get("User")), + 1 + ) + self.assertTrue(self.test_user.email in frappe.flags.webhooks_executed) + self.assertEqual( + frappe.flags.webhooks_executed.get(self.test_user.email)[0], + self.sample_webhooks[0].name + ) + def test_validate_doc_events(self): "Test creating a submit-related webhook for a non-submittable DocType" From 38ca6fe621a03ba4fa33aefef677c9711f46d945 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 15 Apr 2021 10:23:09 +0530 Subject: [PATCH 18/53] style: Remove extra space --- frappe/integrations/doctype/webhook/test_webhook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py index 6c6e4c1a4d..acf2f609e7 100644 --- a/frappe/integrations/doctype/webhook/test_webhook.py +++ b/frappe/integrations/doctype/webhook/test_webhook.py @@ -38,7 +38,7 @@ class TestWebhook(unittest.TestCase): cls.sample_webhooks = [] for wh_fields in samples_webhooks_data: - wh = frappe.new_doc("Webhook") + wh = frappe.new_doc("Webhook") wh.update(wh_fields) wh.insert() cls.sample_webhooks.append(wh) From 9830406afa14f94be65d6e3c7c506e0d461857ab Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 15 Apr 2021 11:55:48 +0530 Subject: [PATCH 19/53] test: Add test to check group mentions --- frappe/core/doctype/user/test_user.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index 5b16c72775..deea241617 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -229,6 +229,28 @@ class TestUser(unittest.TestCase): self.assertEqual(extract_mentions(comment)[0], "test_user@example.com") self.assertEqual(extract_mentions(comment)[1], "test.again@example1.com") + doc = frappe.get_doc({ + 'doctype': 'User Group', + 'group_name': 'Team', + 'user_group_members': [{ + 'user': 'test@example.com' + }, { + 'user': 'test1@example.com' + }] + }) + doc.insert(ignore_if_duplicate=True) + + comment = ''' +
+ Testing comment for + + @Team + + please check +
+ ''' + self.assertListEqual(extract_mentions(comment), ['test@example.com', 'test1@example.com']) + def test_rate_limiting_for_reset_password(self): # Allow only one reset request for a day frappe.db.set_value("System Settings", "System Settings", "password_reset_limit", 1) From ea2a2772f287e6038b3895e6b15d83a9a06bc248 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 15 Apr 2021 12:39:15 +0530 Subject: [PATCH 20/53] test: which email Id to pick for sending emails (#12550) Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/email/test_smtp.py | 55 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/frappe/email/test_smtp.py b/frappe/email/test_smtp.py index 869d708430..0b11c559a2 100644 --- a/frappe/email/test_smtp.py +++ b/frappe/email/test_smtp.py @@ -2,7 +2,9 @@ # License: The MIT License import unittest +import frappe from frappe.email.smtp import SMTPServer +from frappe.email.smtp import get_outgoing_email_account class TestSMTP(unittest.TestCase): def test_smtp_ssl_session(self): @@ -13,6 +15,57 @@ class TestSMTP(unittest.TestCase): for port in [None, 0, 587, "587"]: make_server(port, 0, 1) + def test_get_email_account(self): + existing_email_accounts = frappe.get_all("Email Account", fields = ["name", "enable_outgoing", "default_outgoing", "append_to"]) + unset_details = { + "enable_outgoing": 0, + "default_outgoing": 0, + "append_to": None + } + for email_account in existing_email_accounts: + frappe.db.set_value('Email Account', email_account['name'], unset_details) + + # remove mail_server config so that test@example.com is not created + mail_server = frappe.conf.get('mail_server') + del frappe.conf['mail_server'] + + frappe.local.outgoing_email_account = {} + + frappe.local.outgoing_email_account = {} + # lowest preference given to email account with default incoming enabled + create_email_account(email_id="default_outgoing_enabled@gmail.com", password="***", enable_outgoing = 1, default_outgoing=1) + self.assertEqual(get_outgoing_email_account().email_id, "default_outgoing_enabled@gmail.com") + + frappe.local.outgoing_email_account = {} + # highest preference given to email account with append_to matching + create_email_account(email_id="append_to@gmail.com", password="***", enable_outgoing = 1, default_outgoing=1, append_to="Blog Post") + self.assertEqual(get_outgoing_email_account(append_to="Blog Post").email_id, "append_to@gmail.com") + + # add back the mail_server + frappe.conf['mail_server'] = mail_server + for email_account in existing_email_accounts: + set_details = { + "enable_outgoing": email_account['enable_outgoing'], + "default_outgoing": email_account['default_outgoing'], + "append_to": email_account['append_to'] + } + frappe.db.set_value('Email Account', email_account['name'], set_details) + +def create_email_account(email_id, password, enable_outgoing, default_outgoing=0, append_to=None): + email_dict = { + "email_id": email_id, + "passsword": password, + "enable_outgoing":enable_outgoing , + "default_outgoing":default_outgoing , + "enable_incoming": 1, + "append_to":append_to, + "is_dummy_password": 1, + "smtp_server": "localhost" + } + + email_account = frappe.new_doc('Email Account') + email_account.update(email_dict) + email_account.save() def make_server(port, ssl, tls): server = SMTPServer( @@ -22,4 +75,4 @@ def make_server(port, ssl, tls): use_tls = tls ) - server.sess \ No newline at end of file + server.sess From c7c2362f9c7d007d07b927223663f551e557f03e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 15 Apr 2021 12:44:36 +0530 Subject: [PATCH 21/53] fix: Redirect Web Form user directly to success URL, if no amount is due (backport #12661) (#12856) If there is no amount due when using WebForms which accept payments, redirect the user directly to the success URL. This is useful for free items. (cherry picked from commit f8f1301b796180bca6b860bde5e01fa85a632c1f) Co-authored-by: ci2014 --- frappe/website/doctype/web_form/web_form.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 49cbad5658..f78aaac934 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -215,6 +215,11 @@ def get_context(context): amount = self.amount if self.amount_based_on_field: amount = doc.get(self.amount_field) + + from decimal import Decimal + if amount is None or Decimal(amount) <= 0: + return frappe.utils.get_url(self.success_url or self.route) + payment_details = { "amount": amount, "title": title, From 219b60a6b101664865925ab5b046a9d5364da462 Mon Sep 17 00:00:00 2001 From: leela Date: Thu, 15 Apr 2021 13:10:16 +0530 Subject: [PATCH 22/53] fix: add field type change check in custom field validation We have fieldtype change check in customizeform but not in custom field. Added the same for customfield. --- .../doctype/custom_field/custom_field.py | 8 ++++- .../doctype/customize_form/customize_form.py | 34 +++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index ee6e3b9c61..3126326636 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -40,6 +40,8 @@ class CustomField(Document): frappe.throw(_("A field with the name '{}' already exists in doctype {}.").format(self.fieldname, self.dt)) def validate(self): + from frappe.custom.doctype.customize_form.customize_form import CustomizeForm + meta = frappe.get_meta(self.dt, cached=False) fieldnames = [df.fieldname for df in meta.get("fields")] @@ -49,7 +51,11 @@ class CustomField(Document): if self.insert_after and self.insert_after in fieldnames: self.idx = fieldnames.index(self.insert_after) + 1 - self._old_fieldtype = self.db_get('fieldtype') + old_fieldtype = self.db_get('fieldtype') + is_fieldtype_changed = (not self.is_new()) and (old_fieldtype != self.fieldtype) + + if is_fieldtype_changed and not CustomizeForm.allow_fieldtype_change(old_fieldtype, self.fieldtype): + frappe.throw(_("Fieldtype cannot be changed from {0} to {1}").format(old_fieldtype, self.fieldtype)) if not self.fieldname: frappe.throw(_("Fieldname not set for Custom Field")) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index c79c965aae..9f6996a660 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -401,22 +401,18 @@ class CustomizeForm(Document): return property_value def validate_fieldtype_change(self, df, old_value, new_value): - allowed = False - self.check_length_for_fieldtypes = [] - for allowed_changes in ALLOWED_FIELDTYPE_CHANGE: - if (old_value in allowed_changes and new_value in allowed_changes): - allowed = True - old_value_length = cint(frappe.db.type_map.get(old_value)[1]) - new_value_length = cint(frappe.db.type_map.get(new_value)[1]) + allowed = self.allow_fieldtype_change(old_value, new_value) + if allowed: + old_value_length = cint(frappe.db.type_map.get(old_value)[1]) + new_value_length = cint(frappe.db.type_map.get(new_value)[1]) - # Ignore fieldtype check validation if new field type has unspecified maxlength - # Changes like DATA to TEXT, where new_value_lenth equals 0 will not be validated - if new_value_length and (old_value_length > new_value_length): - self.check_length_for_fieldtypes.append({'df': df, 'old_value': old_value}) - self.validate_fieldtype_length() - else: - self.flags.update_db = True - break + # Ignore fieldtype check validation if new field type has unspecified maxlength + # Changes like DATA to TEXT, where new_value_lenth equals 0 will not be validated + if new_value_length and (old_value_length > new_value_length): + self.check_length_for_fieldtypes.append({'df': df, 'old_value': old_value}) + self.validate_fieldtype_length() + else: + self.flags.update_db = True if not allowed: frappe.throw(_("Fieldtype cannot be changed from {0} to {1} in row {2}").format(old_value, new_value, df.idx)) @@ -458,6 +454,14 @@ class CustomizeForm(Document): reset_customization(self.doc_type) self.fetch_to_customize() + @classmethod + def allow_fieldtype_change(self, old_type: str, new_type: str) -> bool: + """ allow type change, if both old_type and new_type are in same field group. + field groups are defined in ALLOWED_FIELDTYPE_CHANGE variables. + """ + in_field_group = lambda group: (old_type in group) and (new_type in group) + return any(map(in_field_group, ALLOWED_FIELDTYPE_CHANGE)) + def reset_customization(doctype): setters = frappe.get_all("Property Setter", filters={ 'doc_type': doctype, From 6d20983eb27424505e0874bf317b0d586fdbcdb8 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 15 Apr 2021 14:15:28 +0530 Subject: [PATCH 23/53] fix(minor): Make language select optional and fix breakpoint issues in website --- frappe/public/scss/website/index.scss | 7 +++ frappe/public/scss/website/navbar.scss | 14 ++++- frappe/templates/base.html | 36 +------------ frappe/templates/includes/navbar/navbar.html | 4 +- .../website_settings/website_settings.json | 21 ++++++-- .../website_settings/website_settings.py | 7 +-- frappe/website/js/website.js | 53 ++++++++++++++----- 7 files changed, 85 insertions(+), 57 deletions(-) diff --git a/frappe/public/scss/website/index.scss b/frappe/public/scss/website/index.scss index 1fb5badc6c..823ec9b08a 100644 --- a/frappe/public/scss/website/index.scss +++ b/frappe/public/scss/website/index.scss @@ -90,6 +90,13 @@ margin: 2rem 0; } +@media (max-width: map-get($grid-breakpoints, "lg")) { + .page-content-wrapper .container { + padding-left: 1rem; + padding-right: 1rem; + } +} + .breadcrumb-container { margin-top: 1rem; padding-top: 0.25rem; diff --git a/frappe/public/scss/website/navbar.scss b/frappe/public/scss/website/navbar.scss index 4d2ccfece9..3496a8907c 100644 --- a/frappe/public/scss/website/navbar.scss +++ b/frappe/public/scss/website/navbar.scss @@ -1,3 +1,15 @@ +.navbar { + padding-left: 0; + padding-right: 0; +} + +@media (max-width: map-get($grid-breakpoints, "lg")) { + .navbar { + padding-left: 1rem; + padding-right: 1rem; + } +} + .navbar-light { border-bottom: 1px solid $border-color; background: $navbar-bg; @@ -96,4 +108,4 @@ @extend .ellipsis; max-width: 100%; vertical-align: middle; -} \ No newline at end of file +} diff --git a/frappe/templates/base.html b/frappe/templates/base.html index 18c9e9d99a..78aa573c99 100644 --- a/frappe/templates/base.html +++ b/frappe/templates/base.html @@ -56,6 +56,8 @@ } window.dev_server = {{ dev_server }}; window.socketio_port = {{ (frappe.socketio_port or 'null') }}; + window.show_language_picker = {{ show_language_picker }}; + window.is_chat_enabled = {{ chat_enable }}; @@ -110,39 +112,5 @@ {%- endblock %} {%- block body_include %}{{ body_include or "" }}{% endblock -%} - diff --git a/frappe/templates/includes/navbar/navbar.html b/frappe/templates/includes/navbar/navbar.html index 7856413602..1fb4ae9fb0 100644 --- a/frappe/templates/includes/navbar/navbar.html +++ b/frappe/templates/includes/navbar/navbar.html @@ -21,8 +21,8 @@ -
- +
+
diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index 3ca02e2a37..9e04cf3795 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -25,9 +25,11 @@ "set_banner_from_image", "favicon", "top_bar", - "navbar_search", - "hide_login", "top_bar_items", + "hide_login", + "navbar_search", + "show_language_picker", + "navbar_template_section", "navbar_template", "navbar_template_values", "edit_navbar_template_values", @@ -410,6 +412,19 @@ "fieldname": "google_analytics_anonymize_ip", "fieldtype": "Check", "label": "Google Analytics Anonymize IP" + }, + { + "default": "0", + "fieldname": "show_language_picker", + "fieldtype": "Check", + "label": "Show Language Picker" + }, + { + "collapsible": 1, + "collapsible_depends_on": "navbar_template", + "fieldname": "navbar_template_section", + "fieldtype": "Section Break", + "label": "Navbar Template" } ], "icon": "fa fa-cog", @@ -418,7 +433,7 @@ "issingle": 1, "links": [], "max_attachments": 10, - "modified": "2021-04-13 10:22:51.888788", + "modified": "2021-04-14 17:39:56.609771", "modified_by": "Administrator", "module": "Website", "name": "Website Settings", diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index 89def9bf8d..f7f22aa2df 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -121,7 +121,8 @@ def get_website_settings(context=None): "facebook_share", "google_plus_one", "twitter_share", "linked_in_share", "disable_signup", "hide_footer_signup", "head_html", "title_prefix", "navbar_template", "footer_template", "navbar_search", "enable_view_tracking", - "footer_logo", "call_to_action", "call_to_action_url"]: + "footer_logo", "call_to_action", "call_to_action_url", "show_language_picker", + "chat_enable"]: if hasattr(settings, k): context[k] = settings.get(k) @@ -178,7 +179,3 @@ def get_items(parentfield): t['child_items'].append(d) break return top_items - -@frappe.whitelist(allow_guest=True) -def is_chat_enabled(): - return bool(frappe.db.get_single_value('Website Settings', 'chat_enable')) diff --git a/frappe/website/js/website.js b/frappe/website/js/website.js index b8360e68ca..99d3fbf650 100644 --- a/frappe/website/js/website.js +++ b/frappe/website/js/website.js @@ -376,6 +376,39 @@ $.extend(frappe, { // Start observing an element io.observe(el); }); + }, + show_language_picker() { + if (frappe.session.user === 'Guest' && window.show_language_picker) { + frappe.call("frappe.translate.get_all_languages", { + with_language_name: true + }).then(res => { + let language_list = res.message; + let language = frappe.get_cookie('preferred_language'); + let language_codes = []; + let language_switcher = $("#language-switcher .form-control") + language_list.forEach(language_doc => { + language_codes.push(language_doc.language_code) + language_switcher + .append( + $("") + .attr("value", language_doc.language_code) + .text(language_doc.language_name) + ); + }); + $("#language-switcher").removeClass('hide'); + language = language || (language_codes.includes(navigator.language) ? navigator.language : 'en'); + language_switcher.val(language); + document.documentElement.lang = language; + language_switcher.change((e) => { + let lang = language_switcher.val(); + frappe.call("frappe.translate.set_preferred_language_cookie", { + "preferred_language": lang + }).then(() => { + window.location.reload(); + }); + }); + }); + } } }); @@ -599,17 +632,13 @@ $(document).on("page-change", function() { frappe.ready(function() { - frappe.call({ - method: 'frappe.website.doctype.website_settings.website_settings.is_chat_enabled', - callback: (r) => { - if (r.message) { - frappe.require(['/assets/js/moment-bundle.min.js', "/assets/css/frappe-chat-web.css", "/assets/frappe/js/lib/socket.io.min.js"], () => { - frappe.require('/assets/js/chat.js', () => { - frappe.chat.setup(); - }); - }); - } - } - }); + frappe.show_language_picker(); + if (window.is_chat_enabled) { + frappe.require(['/assets/js/moment-bundle.min.js', "/assets/css/frappe-chat-web.css", "/assets/frappe/js/lib/socket.io.min.js"], () => { + frappe.require('/assets/js/chat.js', () => { + frappe.chat.setup(); + }); + }); + } frappe.socketio.init(window.socketio_port); }); From 8a5e8b53e8f10aa8acb8b4a2d5f89c9a7a7a647b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 15 Apr 2021 14:22:52 +0530 Subject: [PATCH 24/53] fix: Make group_name and user group members mandatory --- frappe/core/doctype/user/user.py | 7 +++++-- frappe/core/doctype/user_group/user_group.json | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 035333cbe9..b9889026fe 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1019,8 +1019,11 @@ def extract_mentions(txt): emails = [] for mention in soup.find_all(class_='mention'): if mention.get('data-is-group'): - user_group = frappe.get_cached_doc('User Group', mention['data-id']) - emails += [d.user for d in user_group.user_group_members] + try: + user_group = frappe.get_cached_doc('User Group', mention['data-id']) + emails += [d.user for d in user_group.user_group_members] + except frappe.DoesNotExistError: + pass continue email = mention['data-id'] emails.append(email) diff --git a/frappe/core/doctype/user_group/user_group.json b/frappe/core/doctype/user_group/user_group.json index 3d859dcc9a..64f2801e15 100644 --- a/frappe/core/doctype/user_group/user_group.json +++ b/frappe/core/doctype/user_group/user_group.json @@ -13,19 +13,22 @@ { "fieldname": "group_name", "fieldtype": "Data", + "in_list_view": 1, "label": "Group Name", + "reqd": 1, "unique": 1 }, { "fieldname": "user_group_members", "fieldtype": "Table MultiSelect", "label": "User Group Members", - "options": "User Group Member" + "options": "User Group Member", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-04-12 15:17:24.751710", + "modified": "2021-04-15 12:17:04.625640", "modified_by": "Administrator", "module": "Core", "name": "User Group", From 4f8ced5b1b2f1d8cabddd8d4d51e60d4df067b4b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 15 Apr 2021 14:27:41 +0530 Subject: [PATCH 25/53] fix: Remove unnecessary field --- frappe/core/doctype/user/test_user.py | 2 +- frappe/core/doctype/user_group/user_group.json | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index deea241617..5bea767934 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -231,7 +231,7 @@ class TestUser(unittest.TestCase): doc = frappe.get_doc({ 'doctype': 'User Group', - 'group_name': 'Team', + 'name': 'Team', 'user_group_members': [{ 'user': 'test@example.com' }, { diff --git a/frappe/core/doctype/user_group/user_group.json b/frappe/core/doctype/user_group/user_group.json index 64f2801e15..21d74ccddf 100644 --- a/frappe/core/doctype/user_group/user_group.json +++ b/frappe/core/doctype/user_group/user_group.json @@ -1,23 +1,14 @@ { "actions": [], - "autoname": "field:group_name", + "autoname": "Prompt", "creation": "2021-04-12 15:17:24.751710", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "group_name", "user_group_members" ], "fields": [ - { - "fieldname": "group_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Group Name", - "reqd": 1, - "unique": 1 - }, { "fieldname": "user_group_members", "fieldtype": "Table MultiSelect", @@ -28,7 +19,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-04-15 12:17:04.625640", + "modified": "2021-04-15 14:24:23.168485", "modified_by": "Administrator", "module": "Core", "name": "User Group", From 27ed3dd37303c1678f006b92a006e269900786d7 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 15 Apr 2021 14:31:42 +0530 Subject: [PATCH 26/53] fix(minor): linting --- frappe/website/js/website.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/website/js/website.js b/frappe/website/js/website.js index 99d3fbf650..ea0b9aedfa 100644 --- a/frappe/website/js/website.js +++ b/frappe/website/js/website.js @@ -385,9 +385,9 @@ $.extend(frappe, { let language_list = res.message; let language = frappe.get_cookie('preferred_language'); let language_codes = []; - let language_switcher = $("#language-switcher .form-control") + let language_switcher = $("#language-switcher .form-control"); language_list.forEach(language_doc => { - language_codes.push(language_doc.language_code) + language_codes.push(language_doc.language_code); language_switcher .append( $("") @@ -399,7 +399,7 @@ $.extend(frappe, { language = language || (language_codes.includes(navigator.language) ? navigator.language : 'en'); language_switcher.val(language); document.documentElement.lang = language; - language_switcher.change((e) => { + language_switcher.change(() => { let lang = language_switcher.val(); frappe.call("frappe.translate.set_preferred_language_cookie", { "preferred_language": lang From 8678c09e910b193fa9b1239fae75373bed62a1c8 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 15 Apr 2021 14:34:23 +0530 Subject: [PATCH 27/53] feat: Add log_error and FrappeClient to restricted python (#12857) --- frappe/utils/safe_exec.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 29bded6fc8..6c1fa21685 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -12,6 +12,7 @@ from frappe.modules import scrub from frappe.www.printview import get_visible_columns import frappe.exceptions import frappe.integrations.utils +from frappe.frappeclient import FrappeClient class ServerScriptNotEnabled(frappe.PermissionError): pass @@ -104,8 +105,10 @@ def get_safe_globals(): make_post_request = frappe.integrations.utils.make_post_request, socketio_port=frappe.conf.socketio_port, get_hooks=frappe.get_hooks, - sanitize_html=frappe.utils.sanitize_html + sanitize_html=frappe.utils.sanitize_html, + log_error=frappe.log_error ), + FrappeClient=FrappeClient, style=frappe._dict( border_color='#d1d8dd' ), @@ -297,4 +300,4 @@ VALID_UTILS = ( "formatdate", "get_user_info_for_avatar", "get_abbr" -) \ No newline at end of file +) From f71a6c09f503bac8028f16eccd6fd2016c7d12be Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 15 Apr 2021 14:53:01 +0530 Subject: [PATCH 28/53] fix: Sync user groups realtime --- frappe/core/doctype/user_group/user_group.py | 7 ++++++- frappe/public/js/frappe/desk.js | 13 +++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/user_group/user_group.py b/frappe/core/doctype/user_group/user_group.py index 6fc6db4620..64bffa06d0 100644 --- a/frappe/core/doctype/user_group/user_group.py +++ b/frappe/core/doctype/user_group/user_group.py @@ -5,6 +5,11 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document +import frappe class UserGroup(Document): - pass + def after_insert(self): + frappe.publish_realtime('user_group_added', self.name) + + def on_trash(self): + frappe.publish_realtime('user_group_deleted', self.name) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 250d308b7e..6ceac48a8c 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -113,7 +113,7 @@ frappe.Application = Class.extend({ dialog.get_close_btn().toggle(false); }); - this.setup_social_listeners(); + this.setup_user_group_listeners(); // listen to build errors this.setup_build_error_listener(); @@ -592,11 +592,12 @@ frappe.Application = Class.extend({ } }, - setup_social_listeners() { - frappe.realtime.on('mention', (message) => { - if (frappe.get_route()[0] !== 'social') { - frappe.show_alert(message); - } + setup_user_group_listeners() { + frappe.realtime.on('user_group_added', (user_group) => { + frappe.boot.user_groups && frappe.boot.user_groups.push(user_group); + }); + frappe.realtime.on('user_group_deleted', (user_group) => { + frappe.boot.user_groups = (frappe.boot.user_groups || []).filter(el => el !== user_group); }); }, From b2649eda1ee350c957d4899672ea40c12e817203 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 15 Apr 2021 16:07:03 +0530 Subject: [PATCH 29/53] fix: Use css variables for mention colors --- frappe/public/scss/common/quill.scss | 6 +++--- frappe/public/scss/desk/css_variables.scss | 4 ++++ frappe/public/scss/desk/dark.scss | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frappe/public/scss/common/quill.scss b/frappe/public/scss/common/quill.scss index c4011e2d6b..ce4a10d190 100644 --- a/frappe/public/scss/common/quill.scss +++ b/frappe/public/scss/common/quill.scss @@ -119,7 +119,7 @@ border: 1px solid var(--border-color); padding: 2px 5px; font-size: var(--text-sm); - background-color: var(--fg-color); + background-color: var(--user-mention-bg-color); } // table @@ -174,7 +174,7 @@ .ql-editor.read-mode { padding: 0; .mention { - background-color: var(--control-bg); + --user-mention-bg-color: var(--control-bg); } } @@ -193,5 +193,5 @@ } .mention[data-is-group="true"] { - background-color: var(--purple-100) !important; + background-color: var(--group-mention-bg-color); } diff --git a/frappe/public/scss/desk/css_variables.scss b/frappe/public/scss/desk/css_variables.scss index 21b4ac6c1d..5aca23a0b0 100644 --- a/frappe/public/scss/desk/css_variables.scss +++ b/frappe/public/scss/desk/css_variables.scss @@ -59,6 +59,10 @@ $input-height: 28px !default; --timeline-content-max-width: 700px; --timeline-left-padding: calc(var(--padding-xl) + var(--timeline-item-icon-size) / 2); + // mentions + --user-mention-bg-color: var(--fg-color); + --group-mention-bg-color: var(--bg-purple); + // skeleton --skeleton-bg: var(--gray-100); diff --git a/frappe/public/scss/desk/dark.scss b/frappe/public/scss/desk/dark.scss index 743107af47..5817e33ca0 100644 --- a/frappe/public/scss/desk/dark.scss +++ b/frappe/public/scss/desk/dark.scss @@ -99,7 +99,7 @@ .ql-editor { color: var(--text-on-gray); &.read-mode { - span, + span:not(.mention), p, u, strong { From 1bfeafca5f862eeeb0848357e0fbae567483796d Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 15 Apr 2021 16:08:18 +0530 Subject: [PATCH 30/53] fix: Check if `data-is-group` should be `true` before getting doc --- frappe/core/doctype/user/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index b9889026fe..0462de8643 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1018,7 +1018,7 @@ def extract_mentions(txt): soup = BeautifulSoup(txt, 'html.parser') emails = [] for mention in soup.find_all(class_='mention'): - if mention.get('data-is-group'): + if mention.get('data-is-group') == 'true': try: user_group = frappe.get_cached_doc('User Group', mention['data-id']) emails += [d.user for d in user_group.user_group_members] From 7d335a9beffef41d736bd7d92057b7b66e1fb655 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 15 Apr 2021 16:22:17 +0530 Subject: [PATCH 31/53] fix: Make group mention clickable --- frappe/core/doctype/user_group/user_group.json | 6 +++++- frappe/public/js/frappe/utils/utils.js | 3 ++- frappe/public/scss/common/quill.scss | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user_group/user_group.json b/frappe/core/doctype/user_group/user_group.json index 21d74ccddf..e807372061 100644 --- a/frappe/core/doctype/user_group/user_group.json +++ b/frappe/core/doctype/user_group/user_group.json @@ -19,7 +19,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-04-15 14:24:23.168485", + "modified": "2021-04-15 16:12:31.455401", "modified_by": "Administrator", "module": "Core", "name": "User Group", @@ -36,6 +36,10 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "read": 1, + "role": "All" } ], "sort_field": "modified", diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index e1e4f437cd..7ce30a525c 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1290,7 +1290,8 @@ Object.assign(frappe.utils, { names_for_mentions.push({ id: group, value: group, - is_group: true + is_group: true, + link: frappe.utils.get_form_link('User Group', group) }); }); diff --git a/frappe/public/scss/common/quill.scss b/frappe/public/scss/common/quill.scss index ce4a10d190..d15ca7e036 100644 --- a/frappe/public/scss/common/quill.scss +++ b/frappe/public/scss/common/quill.scss @@ -120,6 +120,9 @@ padding: 2px 5px; font-size: var(--text-sm); background-color: var(--user-mention-bg-color); + a[href] { + text-decoration: none; + } } // table From ccadda21d501867f8a2be1a6457eca3912dcc949 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 15 Apr 2021 16:33:07 +0530 Subject: [PATCH 32/53] fix: Multi-column paste in grid (#12861) --- frappe/public/js/frappe/form/controls/table.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/table.js b/frappe/public/js/frappe/form/controls/table.js index 075608aa8c..c40f471939 100644 --- a/frappe/public/js/frappe/form/controls/table.js +++ b/frappe/public/js/frappe/form/controls/table.js @@ -45,9 +45,12 @@ frappe.ui.form.ControlTable = frappe.ui.form.Control.extend({ } else { // no column header, map to the existing visible columns const visible_columns = grid_rows[0].get_visible_columns(); + let target_column_matched = false; visible_columns.forEach(column => { - if (column.fieldname === $(e.target).data('fieldname')) { + // consider all columns after the target column. + if (target_column_matched || column.fieldname === $(e.target).data('fieldname')) { fieldnames.push(column.fieldname); + target_column_matched = true; } }); } From d216acaaaec3e872fc78bf1e4ef5c6d5f9bb9ce1 Mon Sep 17 00:00:00 2001 From: walstanb Date: Thu, 15 Apr 2021 19:28:47 +0530 Subject: [PATCH 33/53] fix: attachment pill lock icon redirects to File --- .../public/js/frappe/form/sidebar/attachments.js | 16 +++------------- .../js/frappe/form/templates/attachment.html | 10 ---------- 2 files changed, 3 insertions(+), 23 deletions(-) delete mode 100644 frappe/public/js/frappe/form/templates/attachment.html diff --git a/frappe/public/js/frappe/form/sidebar/attachments.js b/frappe/public/js/frappe/form/sidebar/attachments.js index 9e1ea30c6e..ffd0b513a2 100644 --- a/frappe/public/js/frappe/form/sidebar/attachments.js +++ b/frappe/public/js/frappe/form/sidebar/attachments.js @@ -1,8 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt - - frappe.ui.form.Attachments = Class.extend({ init: function(opts) { $.extend(this, opts); @@ -84,17 +82,9 @@ frappe.ui.form.Attachments = Class.extend({ }; } - let icon; - // REDESIGN-TODO: set icon using frappe.utils.icon - if (attachment.is_private) { - icon = `
- -
`; - } else { - icon = `
- -
`; - } + const icon = ` + ${frappe.utils.icon(attachment.is_private ? 'lock' : 'unlock', 'sm ml-0')} + `; $(`
  • `) .append(frappe.get_data_pill( diff --git a/frappe/public/js/frappe/form/templates/attachment.html b/frappe/public/js/frappe/form/templates/attachment.html deleted file mode 100644 index c1fe3f3c85..0000000000 --- a/frappe/public/js/frappe/form/templates/attachment.html +++ /dev/null @@ -1,10 +0,0 @@ -
  • - × - - - - - {{ file_name }} - -
  • - From f9742944843965d71e050b9f676e87f9b3e2d9f7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 15 Apr 2021 22:12:48 +0530 Subject: [PATCH 34/53] feat: Copy DocType / Documents without boundaries --- frappe/public/js/frappe/desk.js | 31 +++++++++++++++++++++++++ frappe/public/js/frappe/form/toolbar.js | 7 +++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 250d308b7e..c6190230e4 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -51,6 +51,7 @@ frappe.Application = Class.extend({ this.set_fullwidth_if_enabled(); this.add_browser_class(); this.setup_energy_point_listeners(); + this.copy_doc_listeners(); frappe.ui.keys.setup(); @@ -605,6 +606,36 @@ frappe.Application = Class.extend({ frappe.show_alert(message); }); }, + + copy_doc_listeners() { + $('body').on('paste', (e) => { + try { + let clipboard_data = e.clipboardData || window.clipboardData || e.originalEvent.clipboardData; + let pasted_data = clipboard_data.getData('Text'); + let doc = JSON.parse(pasted_data); + if (doc.doctype) { + e.preventDefault(); + let sleep = (time) => { + return new Promise((resolve) => setTimeout(resolve, time)); + }; + frappe.dom.freeze(__('Copying {0}', [doc.doctype])); + + sleep(500).then(() => { + frappe.model.with_doctype(doc.doctype, () => { + let newdoc = frappe.model.copy_doc(doc); + newdoc.__newname = doc.name; + newdoc.idx = null; + newdoc.__run_link_triggers = false; + frappe.set_route('Form', newdoc.doctype, newdoc.name); + frappe.dom.unfreeze(); + }); + }); + } + } catch (e) { + // + } + }); + } }); frappe.get_module = function(m, default_module) { diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 2f5b84fb1a..080ff504f0 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -278,13 +278,18 @@ frappe.ui.form.Toolbar = class Toolbar { }, true) } - // copy + // duplicate if(in_list(frappe.boot.user.can_create, me.frm.doctype) && !me.frm.meta.allow_copy) { this.page.add_menu_item(__("Duplicate"), function() { me.frm.copy_doc(); }, true); } + // copy doc to clipboard + this.page.add_menu_item(__("Copy {0}", [me.frm.doc.doctype]), function() { + frappe.utils.copy_to_clipboard(JSON.stringify(me.frm.doc)); + }, true); + // rename if(this.can_rename()) { this.page.add_menu_item(__("Rename"), function() { From de7087722871b18c2fa56d680638b9b5a6e981a0 Mon Sep 17 00:00:00 2001 From: nikhilponnuru Date: Thu, 15 Apr 2021 21:08:02 +0530 Subject: [PATCH 35/53] fix: Newly created Workspace not being accessible unless a shortcut under Shortcuts is created --- frappe/desk/desktop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 5b6e2fdd21..d1b5e27a2f 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -63,7 +63,7 @@ class Workspace: for section in cards: links = loads(section.get('links')) if isinstance(section.get('links'), string_types) else section.get('links') for item in links: - if self.is_item_allowed(item.get('name'), item.get('type')): + if self.is_item_allowed(item.get('link_to'), item.get('link_type')): return True def _in_active_domains(item): From 73662a50feab196e71e7f4903a3b94f19a3ae1c1 Mon Sep 17 00:00:00 2001 From: leela Date: Fri, 16 Apr 2021 16:21:19 +0530 Subject: [PATCH 36/53] fix: kanban board sync issue Recent refactoring introduced an issue of not syncing board data(comes from reference doctype) into kanban board columns db. Changed to sync it at time of creating kanban board. --- frappe/public/js/frappe/views/kanban/kanban_board.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/views/kanban/kanban_board.js b/frappe/public/js/frappe/views/kanban/kanban_board.js index f563f64cb4..bbc2051e4c 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_board.js +++ b/frappe/public/js/frappe/views/kanban/kanban_board.js @@ -306,6 +306,7 @@ frappe.provide("frappe.views"); store.on('change:cur_list', setup_restore_columns); store.on('change:columns', setup_restore_columns); store.on('change:empty_state', show_empty_state); + fluxify.doAction('update_order'); } function prepare() { From 2bbe4e6461f7e59563c9508e268e24ccdb1f4f72 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 16 Apr 2021 20:53:27 +0530 Subject: [PATCH 37/53] refactor: Use better naming --- frappe/public/js/frappe/desk.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index c6190230e4..82987a5bb2 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -51,7 +51,7 @@ frappe.Application = Class.extend({ this.set_fullwidth_if_enabled(); this.add_browser_class(); this.setup_energy_point_listeners(); - this.copy_doc_listeners(); + this.setup_copy_doc_listener(); frappe.ui.keys.setup(); @@ -607,7 +607,7 @@ frappe.Application = Class.extend({ }); }, - copy_doc_listeners() { + setup_copy_doc_listener() { $('body').on('paste', (e) => { try { let clipboard_data = e.clipboardData || window.clipboardData || e.originalEvent.clipboardData; From 7e3b930138f1ac974192871b178c8b198e633d3f Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 16 Apr 2021 22:14:41 +0530 Subject: [PATCH 38/53] fix: Update labels --- frappe/public/js/frappe/desk.js | 7 +++++-- frappe/public/js/frappe/form/toolbar.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 82987a5bb2..5b95421e1e 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -618,10 +618,12 @@ frappe.Application = Class.extend({ let sleep = (time) => { return new Promise((resolve) => setTimeout(resolve, time)); }; - frappe.dom.freeze(__('Copying {0}', [doc.doctype])); + frappe.dom.freeze(__('Creating {0}', [doc.doctype]) + '...'); + // to avoid abrupt UX + // wait for activity feedback sleep(500).then(() => { - frappe.model.with_doctype(doc.doctype, () => { + let res = frappe.model.with_doctype(doc.doctype, () => { let newdoc = frappe.model.copy_doc(doc); newdoc.__newname = doc.name; newdoc.idx = null; @@ -629,6 +631,7 @@ frappe.Application = Class.extend({ frappe.set_route('Form', newdoc.doctype, newdoc.name); frappe.dom.unfreeze(); }); + res && res.fail(frappe.dom.unfreeze); }); } } catch (e) { diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 080ff504f0..145b8d3eed 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -286,7 +286,7 @@ frappe.ui.form.Toolbar = class Toolbar { } // copy doc to clipboard - this.page.add_menu_item(__("Copy {0}", [me.frm.doc.doctype]), function() { + this.page.add_menu_item(__("Copy to Clipboard"), function() { frappe.utils.copy_to_clipboard(JSON.stringify(me.frm.doc)); }, true); From d12c47681e05e7ce6db7097d82a1963ece82563e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 17 Apr 2021 01:34:11 +0530 Subject: [PATCH 39/53] refactor: frappe.views.CommunicationComposer --- .../js/frappe/form/controls/multiselect.js | 2 +- .../public/js/frappe/views/communication.js | 521 +++++++++--------- 2 files changed, 258 insertions(+), 265 deletions(-) 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 0c294d5869..7cf7b32797 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.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(); - - 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 || ''); - } + this.setup_last_edited_communication(); + this.set_values(); }, - setup_subject_and_recipients: function() { + 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] || ''; + + 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() { 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; - } + const 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() || ""; + function prepend_reply(reply) { + if (me.reply_added === email_template) return; - var parts = content.split(''); + const content_field = me.dialog.fields_dict.content; + const subject_field = me.dialog.fields_dict.subject; - if(parts.length===2) { - content = [reply.message, "
    ", parts[1]]; - } else { - content = [reply.message, "
    ", content]; - } - - 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]; + }, + + set_values: async function () { + for (const fieldname of ["recipients", "cc", "bcc", "sender"]) { + await this.dialog.set_value(fieldname, this[fieldname] || ""); + } + + 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); + } + + for (const fieldname of ['email_template', 'cc', 'bcc']) { + if (this.dialog.get_value(fieldname)) { + this.toggle_more_options(true); + break; } - } - }, - get_last_edited_communication: function() { - if (!frappe.last_edited_communication[this.doc]) { - frappe.last_edited_communication[this.doc] = {}; + 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; } - if(!frappe.last_edited_communication[this.doc][this.key]) { - frappe.last_edited_communication[this.doc][this.key] = {}; - } - - return frappe.last_edited_communication[this.doc][this.key]; + await this.dialog.set_values(last_edited); + this.content_set = true; }, - selected_format: function() { - return this.dialog.fields_dict.select_print_format.input.value || (this.frm && this.frm.meta.default_print_format) || "Standard"; + selected_format() { + return ( + this.dialog.fields_dict.select_print_format.input.value + || this.frm && this.frm.meta.default_print_format + || "Standard" + ); }, - get_print_format: function(format) { + 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'); - }, + set_content: async function() { + if (this.content_set) return; - 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; + let message = this.txt || ""; + if (!message && this.frm) { + const { doctype, docname } = this.frm; + message = await localforage.getItem(doctype + docname) || ""; } - if (signature && !frappe.utils.is_html(signature)) { - signature = signature.replace(/\n/g, "
    "); + if (message) { + this.content_set = true; } - 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(this.real_name) { - this.message = '

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


    " + (this.message || ""); - } - - if(this.message && signature && this.message.includes(signature)) { - signature = ""; - } - - let reply = (this.message || "") + (signature ? ("
    " + signature) : ""); - let content = ''; - - if (this.is_a_reply === 'undefined') { - this.is_a_reply = true; + message += await this.get_signature(); + if (this.real_name && !message.includes("")) { + message = `

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

    +
    ${message}`; } if (this.is_a_reply) { - let last_email = this.last_email; - - if (!last_email) { - last_email = this.frm && this.frm.timeline.get_last_email(true); - } - - 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); - } - - 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; + message += this.get_earlier_reply(); } - fields.content.set_value(content); + + await this.dialog.set_value("content", message); }, - html2text: function(html) { + get_signature: async function () { + 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' + ); + + signature = response.message.signature; + } + + if (!signature) return ""; + + if (!frappe.utils.is_html(signature)) { + signature = signature.replace(/\n/g, "
    "); + } + + return "
    " + signature; + }, + + get_earlier_reply() { + const last_email = ( + this.last_email + || this.frm && this.frm.timeline.get_last_email(true) + ); + + 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(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'); } }); From e4527284d735e7b472bbbf0fad1ed7d299d5335a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 17 Apr 2021 02:12:12 +0530 Subject: [PATCH 40/53] fix: sider issues --- .../public/js/frappe/views/communication.js | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 7cf7b32797..c3e6dfe00a 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -187,7 +187,7 @@ frappe.views.CommunicationComposer = Class.extend({ this.dialog.fields_dict[field].set_data(r.message); } }); - } + }; }); }, @@ -210,12 +210,12 @@ frappe.views.CommunicationComposer = Class.extend({ if (last) { this.subject = last.subject; - if(!this.recipients) { + if (!this.recipients) { this.recipients = last.sender; } // prepend "Re:" - if(strip(this.subject.toLowerCase().split(":")[0])!="re") { + if (strip(this.subject.toLowerCase().split(":")[0])!="re") { this.subject = __("Re: {0}", [this.subject]); } } @@ -304,7 +304,7 @@ frappe.views.CommunicationComposer = Class.extend({ if (this.frm) { $(document).trigger("form-stopped-typing", [this.frm]); } - } + }; }, get_last_edited_communication(clear) { @@ -314,7 +314,6 @@ frappe.views.CommunicationComposer = Class.extend({ 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]; @@ -527,7 +526,7 @@ frappe.views.CommunicationComposer = Class.extend({ // email const fields = this.dialog.fields_dict; - if(this.attach_document_print) { + if (this.attach_document_print) { $(fields.attach_document_print.input).click(); $(fields.select_print_format.wrapper).toggle(true); } @@ -545,7 +544,7 @@ frappe.views.CommunicationComposer = Class.extend({ const me = this; const btn = me.dialog.get_primary_btn(); const form_values = this.get_values(); - if(!form_values) return; + if (!form_values) return; const selected_attachments = $.map($(me.dialog.wrapper).find("[data-file-name]:checked"), function (element) { @@ -553,7 +552,7 @@ frappe.views.CommunicationComposer = Class.extend({ }); - if(form_values.attach_document_print) { + if (form_values.attach_document_print) { me.send_email(btn, form_values, selected_attachments, null, form_values.select_print_format || ""); } else { me.send_email(btn, form_values, selected_attachments); @@ -617,18 +616,18 @@ frappe.views.CommunicationComposer = Class.extend({ const me = this; me.dialog.hide(); - if(!form_values.recipients) { + if (!form_values.recipients) { frappe.msgprint(__("Enter Email Recipient(s)")); return; } - if(!form_values.attach_document_print) { + if (!form_values.attach_document_print) { print_html = null; print_format = null; } - if(this.frm && !frappe.model.can_email(me.doc.doctype, this.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; } @@ -660,10 +659,10 @@ frappe.views.CommunicationComposer = Class.extend({ }, btn, callback(r) { - if(!r.exc) { + if (!r.exc) { frappe.utils.play_sound("email"); - if(r.message["emails_not_sent_to"]) { + if (r.message["emails_not_sent_to"]) { frappe.msgprint(__("Email not sent to {0} (unsubscribed / disabled)", [ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) ); } @@ -680,7 +679,7 @@ frappe.views.CommunicationComposer = Class.extend({ try { me.success(r); } catch (e) { - console.log(e); + console.log(e); // eslint-disable-line } } @@ -692,7 +691,7 @@ frappe.views.CommunicationComposer = Class.extend({ try { me.error(r); } catch (e) { - console.log(e); + console.log(e); // eslint-disable-line } } } @@ -777,14 +776,16 @@ frappe.views.CommunicationComposer = Class.extend({ last_email_content = last_email_content.slice(0, 20 * 1024); } - const communication_date = last_email.communication_date || last_email.creation; + const communication_date = frappe.datetime.global_date_format( + last_email.communication_date || last_email.creation + ); + return `

    ${separator_element || ''} -

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

    +

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

    ${last_email_content}
    From 4a28b2f20285fe4d481a28f50b0473c20147e095 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 17 Apr 2021 02:36:52 +0530 Subject: [PATCH 41/53] fix: set lang to frappe.boot.lang by default --- frappe/public/js/frappe/views/communication.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index c3e6dfe00a..34201a7900 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -387,10 +387,8 @@ frappe.views.CommunicationComposer = Class.extend({ //Load default print language from doctype this.lang_code = doc.language - - if (!this.lang_code && this.get_print_format().default_print_language) { - this.lang_code = this.get_print_format().default_print_language; - } + || this.get_print_format().default_print_language + || frappe.boot.lang; //On selection of language retrieve language code const me = this; From 354e89f4c60e241bbc16b64d0fd6c8dd995f1130 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 17 Apr 2021 03:24:01 +0530 Subject: [PATCH 42/53] fix: clear_cache only on success; use me instead of this --- frappe/public/js/frappe/views/communication.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 34201a7900..ef05ef5857 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -17,16 +17,15 @@ frappe.views.CommunicationComposer = Class.extend({ no_submit_on_enter: true, fields: this.get_fields(), primary_action_label: __("Send"), - size: 'large', primary_action() { me.send_action(); - me.clear_cache(); }, secondary_action_label: __("Discard"), secondary_action() { me.dialog.hide(); me.clear_cache(); }, + size: 'large', minimizable: true }); @@ -665,11 +664,10 @@ frappe.views.CommunicationComposer = Class.extend({ [ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) ); } - if ((frappe.last_edited_communication[me.doc] || {})[me.key]) { - delete frappe.last_edited_communication[me.doc][me.key]; - } - if (this.frm) { - this.frm.reload_doc(); + me.clear_cache(); + + if (me.frm) { + me.frm.reload_doc(); } // try the success callback if it exists From 01cd2308bbc6a5e7029eecc67dd4ab3ac55dd4e5 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 17 Apr 2021 03:53:41 +0530 Subject: [PATCH 43/53] fix: Cannot read property `current` of undefined --- frappe/public/js/frappe/form/form_viewers.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/public/js/frappe/form/form_viewers.js b/frappe/public/js/frappe/form/form_viewers.js index 3d488e4729..964576ef8a 100644 --- a/frappe/public/js/frappe/form/form_viewers.js +++ b/frappe/public/js/frappe/form/form_viewers.js @@ -7,6 +7,11 @@ frappe.ui.form.FormViewers = class FormViewers { refresh() { let users = this.frm.get_docinfo()['viewers']; + if (!users || !users.current || !users.current.length) { + this.parent.empty(); + return; + } + let currently_viewing = users.current.filter(user => user != frappe.session.user); let avatar_group = frappe.avatar_group(currently_viewing, 5, {'align': 'left', 'overlap': true}); this.parent.empty().append(avatar_group); From 47d13a40c754949fc2110ab667dfaad6716c3f3d Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 17 Apr 2021 12:21:19 +0530 Subject: [PATCH 44/53] style: use ES6 class --- .../public/js/frappe/views/communication.js | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index ef05ef5857..77cc91b4ba 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -4,11 +4,12 @@ frappe.last_edited_communication = {}; const separator_element = '
    ---
    '; -frappe.views.CommunicationComposer = Class.extend({ - init(opts) { +frappe.views.CommunicationComposer = class { + constructor(opts) { $.extend(this, opts); this.make(); - }, + } + make() { const me = this; @@ -37,7 +38,7 @@ frappe.views.CommunicationComposer = Class.extend({ if (this.frm) { $(document).trigger('form-typing', [this.frm]); } - }, + } get_fields() { const fields = [ @@ -151,7 +152,7 @@ frappe.views.CommunicationComposer = Class.extend({ } return fields; - }, + } toggle_more_options(show_options) { show_options = show_options || this.dialog.fields_dict.more_options.df.hidden; @@ -159,7 +160,7 @@ frappe.views.CommunicationComposer = Class.extend({ const label = frappe.utils.icon(show_options ? 'up-line': 'down'); this.dialog.get_field('option_toggle_button').set_label(label); - }, + } prepare() { this.setup_multiselect_queries(); @@ -171,7 +172,7 @@ frappe.views.CommunicationComposer = Class.extend({ this.setup_email_template(); this.setup_last_edited_communication(); this.set_values(); - }, + } setup_multiselect_queries() { ['recipients', 'cc', 'bcc'].forEach(field => { @@ -188,7 +189,7 @@ frappe.views.CommunicationComposer = Class.extend({ }); }; }); - }, + } setup_subject_and_recipients() { this.subject = this.subject || ""; @@ -240,7 +241,7 @@ frappe.views.CommunicationComposer = Class.extend({ if (this.frm && !this.recipients) { this.recipients = this.frm.doc[this.frm.email_field]; } - }, + } setup_email_template() { const me = this; @@ -276,7 +277,7 @@ frappe.views.CommunicationComposer = Class.extend({ }, }); } - }, + } setup_last_edited_communication() { if (this.frm) { @@ -304,7 +305,7 @@ frappe.views.CommunicationComposer = Class.extend({ $(document).trigger("form-stopped-typing", [this.frm]); } }; - }, + } get_last_edited_communication(clear) { if (!frappe.last_edited_communication[this.doctype]) { @@ -316,9 +317,9 @@ frappe.views.CommunicationComposer = Class.extend({ } return frappe.last_edited_communication[this.doctype][this.key]; - }, + } - set_values: async function () { + async set_values() { for (const fieldname of ["recipients", "cc", "bcc", "sender"]) { await this.dialog.set_value(fieldname, this[fieldname] || ""); } @@ -341,9 +342,9 @@ frappe.views.CommunicationComposer = Class.extend({ break; } } - }, + } - set_values_from_last_edited_communication: async function () { + async set_values_from_last_edited_communication() { if (this.txt) return; const last_edited = this.get_last_edited_communication(); @@ -358,7 +359,7 @@ frappe.views.CommunicationComposer = Class.extend({ await this.dialog.set_values(last_edited); this.content_set = true; - }, + } selected_format() { return ( @@ -366,7 +367,7 @@ frappe.views.CommunicationComposer = Class.extend({ || this.frm && this.frm.meta.default_print_format || "Standard" ); - }, + } get_print_format(format) { if (!format) { @@ -378,7 +379,7 @@ frappe.views.CommunicationComposer = Class.extend({ } else { return {}; } - }, + } setup_print_language() { const doc = this.frm && this.frm.doc; @@ -403,7 +404,7 @@ frappe.views.CommunicationComposer = Class.extend({ if (this.lang_code) { $(fields.language_sel.input).val(this.lang_code); } - }, + } setup_print() { // print formats @@ -427,7 +428,7 @@ frappe.views.CommunicationComposer = Class.extend({ $(fields.attach_document_print.wrapper).toggle(false); } - }, + } setup_attach() { const fields = this.dialog.fields_dict; @@ -474,7 +475,7 @@ frappe.views.CommunicationComposer = Class.extend({ .find(".add-more-attachments button") .on('click', () => new frappe.ui.FileUploader(args)); this.render_attachment_rows(); - }, + } render_attachment_rows(attachment) { const select_attachments = this.dialog.fields_dict.select_attachments; @@ -500,7 +501,7 @@ frappe.views.CommunicationComposer = Class.extend({ }); } } - }, + } get_attachment_row(attachment, checked) { return $(`

    @@ -517,7 +518,7 @@ frappe.views.CommunicationComposer = Class.extend({ ${frappe.utils.icon('link-url')}

    `); - }, + } setup_email() { // email @@ -535,7 +536,7 @@ frappe.views.CommunicationComposer = Class.extend({ frappe.boot.user.send_me_a_copy = val; }); - }, + } send_action() { const me = this; @@ -554,7 +555,7 @@ frappe.views.CommunicationComposer = Class.extend({ } else { me.send_email(btn, form_values, selected_attachments); } - }, + } get_values() { const form_values = this.dialog.get_values(); @@ -575,7 +576,7 @@ frappe.views.CommunicationComposer = Class.extend({ } return form_values; - }, + } save_as_draft() { if (this.dialog && this.frm) { @@ -590,12 +591,12 @@ frappe.views.CommunicationComposer = Class.extend({ }); } - }, + } clear_cache() { this.delete_saved_draft(); this.get_last_edited_communication(true); - }, + } delete_saved_draft() { if (this.dialog && this.frm) { @@ -607,7 +608,7 @@ frappe.views.CommunicationComposer = Class.extend({ } }); } - }, + } send_email(btn, form_values, selected_attachments, print_html, print_format) { const me = this; @@ -693,7 +694,7 @@ frappe.views.CommunicationComposer = Class.extend({ } } }); - }, + } is_print_letterhead_checked() { if (this.frm && $(this.frm.wrapper).find('.form-print-wrapper').is(':visible')){ @@ -702,9 +703,9 @@ frappe.views.CommunicationComposer = Class.extend({ return (frappe.model.get_doc(":Print Settings", "Print Settings") || { with_letterhead: 1 }).with_letterhead ? 1 : 0; } - }, + } - set_content: async function() { + async set_content() { if (this.content_set) return; let message = this.txt || ""; @@ -728,9 +729,9 @@ frappe.views.CommunicationComposer = Class.extend({ } await this.dialog.set_value("content", message); - }, + } - get_signature: async function () { + async get_signature() { let signature = frappe.boot.user.email_signature; if (!signature) { @@ -750,7 +751,7 @@ frappe.views.CommunicationComposer = Class.extend({ } return "
    " + signature; - }, + } get_earlier_reply() { const last_email = ( @@ -786,7 +787,7 @@ frappe.views.CommunicationComposer = Class.extend({ ${last_email_content}
    `; - }, + } html2text(html) { // convert HTML to text and try and preserve whitespace @@ -798,4 +799,4 @@ frappe.views.CommunicationComposer = Class.extend({ // replace multiple empty lines with just one return d.textContent.replace(/\n{3,}/g, '\n\n'); } -}); +}; From c02fbb27b641153be54dd5025e7b183e19ac181a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 17 Apr 2021 12:24:00 +0530 Subject: [PATCH 45/53] fix: sider issue --- frappe/public/js/frappe/views/communication.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 77cc91b4ba..0479ec6d31 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -276,7 +276,7 @@ frappe.views.CommunicationComposer = class { prepend_reply(r.message); }, }); - } + }; } setup_last_edited_communication() { From 513835a92ce755d5361e1ef8e52269f7561a7983 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 17 Apr 2021 13:12:19 +0530 Subject: [PATCH 46/53] test: no need to blur text editor --- cypress/integration/form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/form.js b/cypress/integration/form.js index 5302ed0964..20ed7a61cd 100644 --- a/cypress/integration/form.js +++ b/cypress/integration/form.js @@ -8,7 +8,7 @@ context('Form', () => { }); it('create a new form', () => { cy.visit('/app/todo/new'); - cy.fill_field('description', 'this is a test todo', 'Text Editor').blur(); + cy.fill_field('description', 'this is a test todo', 'Text Editor'); cy.wait(300); cy.get('.page-title').should('contain', 'Not Saved'); cy.intercept({ From eb45e8775a3d49ccc60c95aca27df0ee550bd7ce Mon Sep 17 00:00:00 2001 From: walstanb Date: Sat, 17 Apr 2021 15:20:12 +0530 Subject: [PATCH 47/53] feat(UI): standardardized themed scrollbar --- frappe/public/scss/common/css_variables.scss | 3 ++ frappe/public/scss/desk/dark.scss | 5 ++++ frappe/public/scss/desk/desktop.scss | 21 ++++++++++++++ frappe/public/scss/desk/index.scss | 1 + frappe/public/scss/desk/scrollbar.scss | 29 ++++++++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 frappe/public/scss/desk/scrollbar.scss diff --git a/frappe/public/scss/common/css_variables.scss b/frappe/public/scss/common/css_variables.scss index 701a0d09e9..8f4af36389 100644 --- a/frappe/public/scss/common/css_variables.scss +++ b/frappe/public/scss/common/css_variables.scss @@ -169,6 +169,9 @@ // Other Colors --sidebar-select-color: var(--gray-200); + --scrollbar-thumb-color: var(--gray-400); + --scrollbar-track-color: var(--gray-200); + --shadow-inset: inset 0px -1px 0px var(--gray-300); --border-color: var(--gray-100); --dark-border-color: var(--gray-300); diff --git a/frappe/public/scss/desk/dark.scss b/frappe/public/scss/desk/dark.scss index 5817e33ca0..4e83f4db47 100644 --- a/frappe/public/scss/desk/dark.scss +++ b/frappe/public/scss/desk/dark.scss @@ -65,6 +65,9 @@ --sidebar-select-color: var(--gray-800); + --scrollbar-thumb-color: var(--gray-600); + --scrollbar-track-color: var(--gray-700); + --shadow-inset: var(--fg-color); --border-color: var(--gray-700); --dark-border-color: var(--gray-600); @@ -75,6 +78,8 @@ // input --input-disabled-bg: none; + color-scheme: dark; + .frappe-card { .btn-default { background-color: var(--bg-color); diff --git a/frappe/public/scss/desk/desktop.scss b/frappe/public/scss/desk/desktop.scss index 1bb91090e6..ac3b1a4f7c 100644 --- a/frappe/public/scss/desk/desktop.scss +++ b/frappe/public/scss/desk/desktop.scss @@ -754,7 +754,28 @@ body { .layout-side-section, .layout-main-section-wrapper { height: 100%; overflow-y: auto; + padding-right: 25px; + scrollbar-color: var(--gray-200) transparent; + [data-theme="dark"] & { + scrollbar-color: var(--gray-800) transparent; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--gray-200); + [data-theme="dark"] & { + background: var(--gray-800); + } + } } + + .layout-side-section { + margin-right: 20px; + } + .desk-sidebar { margin-bottom: var(--margin-2xl); } diff --git a/frappe/public/scss/desk/index.scss b/frappe/public/scss/desk/index.scss index 31eae63776..d0d968df63 100644 --- a/frappe/public/scss/desk/index.scss +++ b/frappe/public/scss/desk/index.scss @@ -10,6 +10,7 @@ @import "mobile"; @import "form"; @import "print_preview"; +@import "scrollbar"; @import "navbar"; @import "../common/modal"; @import "slides"; diff --git a/frappe/public/scss/desk/scrollbar.scss b/frappe/public/scss/desk/scrollbar.scss new file mode 100644 index 0000000000..806ffd13eb --- /dev/null +++ b/frappe/public/scss/desk/scrollbar.scss @@ -0,0 +1,29 @@ +/* Works on Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-track-color); +} + +html { + scrollbar-width: auto; +} + +/* Works on Chrome, Edge, and Safari */ +*::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +*::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb-color); +} + +*::-webkit-scrollbar-track, +*::-webkit-scrollbar-corner { + background: var(--scrollbar-track-color); +} + +body::-webkit-scrollbar { + width: unset; + height: unset; +} From 75822a323a2553c2f2190e12269fd0e36ed1cecc Mon Sep 17 00:00:00 2001 From: mustafaelagamey Date: Sun, 18 Apr 2021 06:46:19 +0200 Subject: [PATCH 48/53] fix: Remove `cmd` only if exist (#12886) * only remove cmd if exist When calling this function from backend this line raises key error as there's no such key called cmd * style: Simplify code Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/desk/treeview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py index 12fdb0dadc..d479b71b52 100644 --- a/frappe/desk/treeview.py +++ b/frappe/desk/treeview.py @@ -66,7 +66,7 @@ def add_node(): doc.save() def make_tree_args(**kwarg): - del kwarg['cmd'] + kwarg.pop('cmd', None) doctype = kwarg['doctype'] parent_field = 'parent_' + doctype.lower().replace(' ', '_') From 2bf968e753bbd35cf9cc37399ffc34adc67c093b Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Sat, 17 Apr 2021 22:54:12 -0600 Subject: [PATCH 49/53] fix: Make strings translatable (#12877) Make strings translatable. --- .../desk/doctype/notification_settings/notification_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/notification_settings/notification_settings.js b/frappe/desk/doctype/notification_settings/notification_settings.js index 88dc145be2..cc2fd95204 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.js +++ b/frappe/desk/doctype/notification_settings/notification_settings.js @@ -19,7 +19,7 @@ frappe.ui.form.on('Notification Settings', { refresh: (frm) => { if (frappe.user.has_role('System Manager')) { - frm.add_custom_button('Go to Notification Settings List', () => { + frm.add_custom_button(__('Go to Notification Settings List'), () => { frappe.set_route('List', 'Notification Settings'); }); } From 0d87ad2133e730c326cc0b7b81565ff5b251b343 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Sun, 18 Apr 2021 11:58:20 +0530 Subject: [PATCH 50/53] fix(minor): Add a delete trigger in grid, and use it to refresh labels in Website Settings --- frappe/public/js/frappe/form/grid.js | 6 +++++- .../website_settings/website_settings.js | 18 +++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index b211476e63..86feefed7a 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -194,7 +194,10 @@ export default class Grid { } tasks.push(() => { - if (dirty) this.refresh(); + if (dirty) { + this.refresh(); + this.frm.script_manager.trigger(this.df.fieldname + "_delete", this.doctype); + } }); frappe.run_serially(tasks); @@ -210,6 +213,7 @@ export default class Grid { this.frm.doc[this.df.fieldname] = []; $(this.parent).find('.rows').empty(); this.grid_rows = []; + this.frm.script_manager.trigger(this.df.fieldname + "_delete", this.doctype); this.wrapper.find('.grid-heading-row .grid-row-check:checked:first').prop('checked', 0); this.refresh(); diff --git a/frappe/website/doctype/website_settings/website_settings.js b/frappe/website/doctype/website_settings/website_settings.js index 422deb244e..2f15b4c00e 100644 --- a/frappe/website/doctype/website_settings/website_settings.js +++ b/frappe/website/doctype/website_settings/website_settings.js @@ -33,20 +33,12 @@ frappe.ui.form.on('Website Settings', { frm.fields_dict.top_bar_items.grid.update_docfield_property( 'parent_label', 'options', frm.events.get_parent_options(frm, "top_bar_items") ); - - if ($(frm.fields_dict.top_bar_items.grid.wrapper).find(".grid-row-open")) { - frm.fields_dict.top_bar_items.grid.refresh(); - } }, set_parent_label_options_footer: function(frm) { frm.fields_dict.footer_items.grid.update_docfield_property( - 'parent_label', 'options', frm.events.get_parent_options(frm, "top_bar_items") + 'parent_label', 'options', frm.events.get_parent_options(frm, "footer_items") ); - - if ($(frm.fields_dict.footer_items.grid.wrapper).find(".grid-row-open")) { - frm.fields_dict.footer_items.grid.refresh(); - } }, authorize_api_indexing_access: function(frm) { @@ -122,10 +114,18 @@ frappe.ui.form.on('Website Settings', { }); frappe.ui.form.on('Top Bar Item', { + top_bar_items_delete(frm) { + frm.events.set_parent_label_options(frm); + }, + footer_items_add(frm, cdt, cdn) { frappe.model.set_value(cdt, cdn, 'right', 0); }, + footer_items_delete(frm) { + frm.events.set_parent_label_options_footer(frm); + }, + parent_label: function(frm, doctype, name) { frm.events.set_parent_options(frm, doctype, name); }, From 33b12d91f988ae7dfb030d1bd30eeeb5acecc48e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sun, 18 Apr 2021 12:38:19 +0530 Subject: [PATCH 51/53] fix: cannot read property `doc` of undefined (#12891) --- .../doctype/communication/communication_list.js | 2 +- frappe/public/js/frappe/views/communication.js | 13 ++++++++----- frappe/public/js/frappe/views/inbox/inbox_view.js | 4 +--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/communication/communication_list.js b/frappe/core/doctype/communication/communication_list.js index 454897b865..315b74a39c 100644 --- a/frappe/core/doctype/communication/communication_list.js +++ b/frappe/core/doctype/communication/communication_list.js @@ -20,6 +20,6 @@ frappe.listview_settings['Communication'] = { }, primary_action: function() { - new frappe.views.CommunicationComposer({ doc: {} }); + new frappe.views.CommunicationComposer(); } }; diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 0479ec6d31..6501018c88 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -7,6 +7,10 @@ const separator_element = '
    ---
    '; frappe.views.CommunicationComposer = class { constructor(opts) { $.extend(this, opts); + if (!this.doc) { + this.doc = this.frm && this.frm.doc || {}; + } + this.make(); } @@ -269,7 +273,7 @@ frappe.views.CommunicationComposer = class { method: 'frappe.email.doctype.email_template.email_template.get_email_template', args: { template_name: email_template, - doc: me.frm.doc, + doc: me.doc, _lang: me.dialog.get_value("language_sel") }, callback(r) { @@ -382,11 +386,10 @@ frappe.views.CommunicationComposer = class { } 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 + this.lang_code = this.doc.language || this.get_print_format().default_print_language || frappe.boot.lang; @@ -612,7 +615,7 @@ frappe.views.CommunicationComposer = class { send_email(btn, form_values, selected_attachments, print_html, print_format) { const me = this; - me.dialog.hide(); + this.dialog.hide(); if (!form_values.recipients) { frappe.msgprint(__("Enter Email Recipient(s)")); @@ -625,7 +628,7 @@ frappe.views.CommunicationComposer = class { } - if (this.frm && !frappe.model.can_email(me.doc.doctype, this.frm)) { + if (this.frm && !frappe.model.can_email(this.doc.doctype, this.frm)) { frappe.msgprint(__("You are not allowed to send emails related to this document")); return; } diff --git a/frappe/public/js/frappe/views/inbox/inbox_view.js b/frappe/public/js/frappe/views/inbox/inbox_view.js index 1085e93e6c..8b53bd49a9 100644 --- a/frappe/public/js/frappe/views/inbox/inbox_view.js +++ b/frappe/public/js/frappe/views/inbox/inbox_view.js @@ -204,9 +204,7 @@ frappe.views.InboxView = class InboxView extends frappe.views.ListView { }; frappe.new_doc('Email Account'); } else { - new frappe.views.CommunicationComposer({ - doc: {} - }); + new frappe.views.CommunicationComposer(); } } }; From c8763859aec2332040b2e0392cfcf5a5fdef327c Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sun, 18 Apr 2021 13:22:43 +0530 Subject: [PATCH 52/53] test: multiple cypress fixes --- cypress/integration/relative_time_filters.js | 3 --- cypress/integration/table_multiselect.js | 2 +- frappe/commands/utils.py | 11 +++++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cypress/integration/relative_time_filters.js b/cypress/integration/relative_time_filters.js index 80e6387d99..cbb0524c24 100644 --- a/cypress/integration/relative_time_filters.js +++ b/cypress/integration/relative_time_filters.js @@ -1,7 +1,4 @@ context('Relative Timeframe', () => { - beforeEach(() => { - cy.login(); - }); before(() => { cy.login(); cy.visit('/app/website'); diff --git a/cypress/integration/table_multiselect.js b/cypress/integration/table_multiselect.js index faa72d63a5..25cab78ba2 100644 --- a/cypress/integration/table_multiselect.js +++ b/cypress/integration/table_multiselect.js @@ -1,5 +1,5 @@ context('Table MultiSelect', () => { - beforeEach(() => { + before(() => { cy.login(); }); diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 5ff66171fc..a203c8c6d9 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -11,7 +11,7 @@ import click import frappe from frappe.commands import get_site, pass_context from frappe.exceptions import SiteNotSpecifiedError -from frappe.utils import get_bench_path, update_progress_bar +from frappe.utils import get_bench_path, update_progress_bar, cint @click.command('build') @@ -567,11 +567,14 @@ def run_ui_tests(context, app, headless=False): node_bin = subprocess.getoutput("npm bin") cypress_path = "{0}/cypress".format(node_bin) - plugin_path = "{0}/cypress-file-upload".format(node_bin) + plugin_path = "{0}/../cypress-file-upload".format(node_bin) # check if cypress in path...if not, install it. - if not (os.path.exists(cypress_path) or os.path.exists(plugin_path)) \ - or not subprocess.getoutput("npm view cypress version").startswith("6."): + if not ( + os.path.exists(cypress_path) + and os.path.exists(plugin_path) + and cint(subprocess.getoutput("npm view cypress version")[:1]) >= 6 + ): # install cypress click.secho("Installing Cypress...", fg="yellow") frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 --no-lockfile") From c16584bbf186183f970583d3793f2e7e670f60f8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 19 Apr 2021 10:45:54 +0530 Subject: [PATCH 53/53] chore: Upgrade frappe-charts to rc13 (#12896) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3c8da66242..6e82890617 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "driver.js": "^0.9.8", "express": "^4.17.1", "fast-deep-equal": "^2.0.1", - "frappe-charts": "^2.0.0-rc11", + "frappe-charts": "^2.0.0-rc13", "frappe-datatable": "^1.15.3", "frappe-gantt": "^0.5.0", "fuse.js": "^3.4.6", diff --git a/yarn.lock b/yarn.lock index 4f6f62ac0a..8ac348011d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2699,10 +2699,10 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -frappe-charts@^2.0.0-rc11: - version "2.0.0-rc11" - resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-2.0.0-rc11.tgz#0724fa0d43593362c075c3805ebbbe1a608fcef7" - integrity sha512-DY3tThT1lNGcJlRMOtIhnILtSm5h1iKysWhZAyj7yrGiOnOWbZpYx/NZzXZYwtRrWwMlYiLX2ylV76qo31ONsg== +frappe-charts@^2.0.0-rc13: + version "2.0.0-rc13" + resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-2.0.0-rc13.tgz#fdb251d7ae311c41e38f90a3ae108070ec6b9072" + integrity sha512-Bv7IfllIrjRbKWHn5b769dOSenqdBixAr6m5kurf8ZUOJSLOgK4HOXItJ7BA8n9PvviH9/k5DaloisjLM2Bm1w== frappe-datatable@^1.15.3: version "1.15.3"