diff --git a/.travis.yml b/.travis.yml index 1551f17ec5..2a60d20c6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,9 @@ services: - mysql install: + - pip install flake8 # pytest + # stop the build if there are Python syntax errors or undefined names + - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics - sudo rm /etc/apt/sources.list.d/docker.list - sudo apt-get purge -y mysql-common mysql-server mysql-client - nvm install v7.10.0 diff --git a/frappe/__init__.py b/frappe/__init__.py index ca148e39a9..28fca585a7 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template -__version__ = '8.5.7' +__version__ = '8.6.2' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/core/doctype/communication/comment.py b/frappe/core/doctype/communication/comment.py index 96c3e59d84..b5f0f141bc 100644 --- a/frappe/core/doctype/communication/comment.py +++ b/frappe/core/doctype/communication/comment.py @@ -82,12 +82,7 @@ def notify_mentions(doc): sender_fullname = get_fullname(frappe.session.user) parent_doc_label = "{0} {1}".format(_(doc.reference_doctype), doc.reference_name) - subject = _("{0} mentioned you in a comment in {1}").format(sender_fullname, parent_doc_label) - message = frappe.get_template("templates/emails/mentioned_in_comment.html").render({ - "sender_fullname": sender_fullname, - "comment": doc, - "link": get_link_to_form(doc.reference_doctype, doc.reference_name, label=parent_doc_label) - }) + subject = _("{0} mentioned you in a comment").format(sender_fullname) recipients = [frappe.db.get_value("User", {"enabled": 1, "username": username, "user_type": "System User"}) for username in mentions] @@ -96,7 +91,13 @@ def notify_mentions(doc): recipients=recipients, sender=frappe.session.user, subject=subject, - message=message + template="mentioned_in_comment", + args={ + "sender_fullname": sender_fullname, + "comment": doc, + "link": get_link_to_form(doc.reference_doctype, doc.reference_name, label=parent_doc_label) + }, + header=[_('New Mention'), 'orange'] ) def get_comments_from_parent(doc): diff --git a/frappe/core/doctype/doctype/boilerplate/controller.py b/frappe/core/doctype/doctype/boilerplate/controller._py similarity index 100% rename from frappe/core/doctype/doctype/boilerplate/controller.py rename to frappe/core/doctype/doctype/boilerplate/controller._py diff --git a/frappe/core/doctype/doctype/boilerplate/test_controller.py b/frappe/core/doctype/doctype/boilerplate/test_controller._py similarity index 100% rename from frappe/core/doctype/doctype/boilerplate/test_controller.py rename to frappe/core/doctype/doctype/boilerplate/test_controller._py diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 2e13e64f57..40207a24dd 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -330,10 +330,10 @@ class DocType(Document): def make_controller_template(self): """Make boilerplate controller template.""" - make_boilerplate("controller.py", self) + make_boilerplate("controller._py", self) if not (self.istable or self.issingle): - make_boilerplate("test_controller.py", self.as_dict()) + make_boilerplate("test_controller._py", self.as_dict()) if not self.istable: make_boilerplate("controller.js", self.as_dict()) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 678e52451a..6a0794c7a1 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -8,13 +8,14 @@ import unittest # test_records = frappe.get_test_records('DocType') + class TestDocType(unittest.TestCase): - def new_doctype(self, name): + def new_doctype(self, name, unique=0): return frappe.get_doc({ "doctype": "DocType", "module": "Core", "custom": 1, - "fields": [{"label": "Some Field", "fieldname": "some_fieldname", "fieldtype": "Data"}], + "fields": [{"label": "Some Field", "fieldname": "some_fieldname", "fieldtype": "Data", "unique": unique}], "permissions": [{"role": "System Manager", "read": 1}], "name": name }) @@ -28,4 +29,28 @@ class TestDocType(unittest.TestCase): frappe.delete_doc("DocType", name) doc = self.new_doctype(name).insert() - doc.delete() \ No newline at end of file + doc.delete() + + def test_doctype_unique_constraint_dropped(self): + if frappe.db.exists("DocType", "With_Unique"): + frappe.delete_doc("DocType", "With_Unique") + + dt = self.new_doctype("With_Unique", unique=1) + dt.insert() + + doc1 = frappe.new_doc("With_Unique") + doc2 = frappe.new_doc("With_Unique") + doc1.some_fieldname = "Something" + doc1.name = "one" + doc2.some_fieldname = "Something" + doc2.name = "two" + + doc1.insert() + self.assertRaises(frappe.UniqueValidationError, doc2.insert) + + dt.fields[0].unique = 0 + dt.save() + + doc2.insert() + doc1.delete() + doc2.delete() diff --git a/frappe/core/doctype/domain/domain.json b/frappe/core/doctype/domain/domain.json index 35b68d0714..f257d02ae8 100644 --- a/frappe/core/doctype/domain/domain.json +++ b/frappe/core/doctype/domain/domain.json @@ -26,7 +26,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Domain", "length": 0, @@ -54,7 +54,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-06-16 13:03:25.430679", + "modified": "2017-07-26 21:29:00.353105", "modified_by": "Administrator", "module": "Core", "name": "Domain", diff --git a/frappe/core/doctype/domain/test_domain.js b/frappe/core/doctype/domain/test_domain.js new file mode 100644 index 0000000000..6d8bd8039d --- /dev/null +++ b/frappe/core/doctype/domain/test_domain.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Domain", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially('Domain', [ + // insert a new Domain + () => frappe.tests.make([ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/core/doctype/feedback_trigger/feedback_trigger.py b/frappe/core/doctype/feedback_trigger/feedback_trigger.py index 6b9ca3e46a..e40f668d58 100644 --- a/frappe/core/doctype/feedback_trigger/feedback_trigger.py +++ b/frappe/core/doctype/feedback_trigger/feedback_trigger.py @@ -66,12 +66,16 @@ def send_feedback_request(reference_doctype, reference_name, trigger="Manual", d feedback_request, url = get_feedback_request_url(reference_doctype, reference_name, details.get("recipients"), trigger) - feedback_url = frappe.render_template("templates/emails/feedback_request_url.html", { "url": url }) + feedback_msg = frappe.render_template("templates/emails/feedback_request_url.html", { "url": url }) # appending feedback url to message body - details.update({ "message": "{message}{feedback_url}".format( + message = "{message}{feedback_msg}".format( message=details.get("message"), - feedback_url=feedback_url) + feedback_msg=feedback_msg + ) + details.update({ + "message": message, + "header": [details.get('subject'), 'blue'] }) if details: diff --git a/frappe/core/doctype/has_role/has_role.py b/frappe/core/doctype/has_role/has_role.py index 44c27098d9..45e76c85a1 100644 --- a/frappe/core/doctype/has_role/has_role.py +++ b/frappe/core/doctype/has_role/has_role.py @@ -7,7 +7,6 @@ import frappe from frappe.model.document import Document class HasRole(Document): - def validate(self): - if cint(self.get("__islocal")) and frappe.db.exists("Has Role", { - "parent": self.parent, "role": self.role}): + def before_insert(self): + if frappe.db.exists("Has Role", {"parent": self.parent, "role": self.role}): frappe.throw(frappe._("User '{0}' already has the role '{1}'").format(self.parent, self.role)) diff --git a/frappe/core/doctype/report/test_query_report.js b/frappe/core/doctype/report/test_query_report.js new file mode 100644 index 0000000000..c51884cd21 --- /dev/null +++ b/frappe/core/doctype/report/test_query_report.js @@ -0,0 +1,33 @@ +// Test for creating query report +QUnit.test("Test Query Report", function(assert){ + assert.expect(2); + let done = assert.async(); + let random = frappe.utils.get_random(10); + frappe.run_serially([ + () => frappe.set_route('List', 'ToDo'), + () => frappe.new_doc('ToDo'), + () => frappe.quick_entry.dialog.set_value('description', random), + () => frappe.quick_entry.insert(), + () => { + return frappe.tests.make('Report', [ + {report_name: 'ToDo List Report'}, + {report_type: 'Query Report'}, + {ref_doctype: 'ToDo'} + ]); + }, + () => frappe.set_route('Form','Report', 'ToDo List Report'), + + //Query + () => cur_frm.set_value('query','select description,owner,status from `tabToDo`'), + () => cur_frm.save(), + () => frappe.set_route('query-report','ToDo List Report'), + () => frappe.timeout(5), + () => { + assert.ok($('div.slick-header-column').length == 4,'Correct numbers of columns visible'); + //To check if the result is present + assert.ok($('div.r1:contains('+random+')').is(':visible'),'Result is visible in report'); + frappe.timeout(3); + }, + () => done() + ]); +}); diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index f90801056d..13c3ac7f0f 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -859,25 +859,21 @@ def notify_admin_access_to_system_manager(login_manager=None): and login_manager.user == "Administrator" and frappe.local.conf.notify_admin_access_to_system_manager): - message = """
- {dear_system_manager}
- {access_message}
- {is_it_unauthorized}
-
%(description)s
" % e - text += ''\ - + frappe._("Daily Event Digest is sent for Calendar Events where reminders are set.")+'
' - - frappe.sendmail(recipients=user.email, subject=frappe._("Upcoming Events for Today"), - content = text) + frappe.sendmail( + recipients=user.email, + subject=frappe._("Upcoming Events for Today"), + template="upcoming_events", + args={ + 'events': events, + }, + header=[frappe._("Events in Today's Calendar"), 'blue'] + ) @frappe.whitelist() def get_events(start, end, user=None, for_reminder=False, filters=None): diff --git a/frappe/desk/page/backups/backups.py b/frappe/desk/page/backups/backups.py index f3517c6953..2d493515b7 100644 --- a/frappe/desk/page/backups/backups.py +++ b/frappe/desk/page/backups/backups.py @@ -76,9 +76,14 @@ def backup_files_and_notify_user(user_email=None): backup_files = backup(with_files=True) get_downloadable_links(backup_files) - subject = "File backup is ready" - message = frappe.render_template('frappe/templates/emails/file_backup_notification.html', backup_files, is_path=True) - frappe.sendmail(recipients=[user_email], subject=subject, message=message) + subject = _("File backup is ready") + frappe.sendmail( + recipients=[user_email], + subject=subject, + template="file_backup_notification", + args=backup_files, + header=[subject, 'green'] + ) def get_downloadable_links(backup_files): for key in ['backup_path_files', 'backup_path_private_files']: diff --git a/frappe/desk/page/chat/chat.py b/frappe/desk/page/chat/chat.py index a0ea8b7393..19f4d4cae4 100644 --- a/frappe/desk/page/chat/chat.py +++ b/frappe/desk/page/chat/chat.py @@ -6,6 +6,7 @@ import frappe from frappe.desk.notifications import delete_notification_count_for from frappe.core.doctype.user.user import STANDARD_USERS from frappe.utils import cint +from frappe import _ @frappe.whitelist() def get_list(arg=None): @@ -132,11 +133,13 @@ def _notify(contact, txt, subject=None): frappe.sendmail(\ recipients=contact, sender= frappe.db.get_value("User", frappe.session.user, "email"), - subject=subject or "New Message from " + get_fullname(frappe.session.user), - message=frappe.get_template("templates/emails/new_message.html").render({ + subject=subject or _("New Message from {0}").format(get_fullname(frappe.session.user)), + template="new_message", + args={ "from": get_fullname(frappe.session.user), "message": txt, "link": get_url() - })) + }, + header=[_('New Message'), 'orange']) except frappe.OutgoingEmailError: pass diff --git a/frappe/desk/page/setup_wizard/setup_wizard.css b/frappe/desk/page/setup_wizard/setup_wizard.css index b17b3676bc..5313a6b4bc 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.css +++ b/frappe/desk/page/setup_wizard/setup_wizard.css @@ -2,25 +2,6 @@ margin-top: 30px; } -.setup-wizard-brand { - margin: 30px; - text-align: center; - display: flex; - justify-content: center; - align-items: center -} - -.setup-wizard-brand .brand-icon { - width: 36px; - height: 36px; -} - -.setup-wizard-brand .brand-name { - font-size: 20px; - margin-left: 8px; - color: #36414C; -} - .setup-wizard-slide { padding-left: 0px; padding-right: 0px; @@ -59,14 +40,6 @@ font-weight: 500; } -.setup-wizard-slide .has-error .control-label { - color: #ffa00a; -} - -.setup-wizard-slide .has-error .form-control{ - border-color: #ffa00a; -} - .setup-wizard-slide .form-control.bold { background-color: #fff; } @@ -113,8 +86,7 @@ .setup-wizard-slide .frappe-control[data-fieldtype="Attach Image"] { width: 140px; height: 180px; /*depends on presence of heading*/ - text-align: center; - margin-left: calc((100% - 140px)/2); + margin-top: 20px; } .setup-wizard-slide .frappe-control[data-fieldtype="Attach Image"] .form-group, diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index e8866f029e..8431df6f8d 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -78,7 +78,6 @@ frappe.setup.Wizard = Class.extend({ ', {html:html})) }, show_working: function() { - $('header').find('.setup-wizard-brand').hide(); this.hide_current_slide(); frappe.set_route(this.page_name); this.current_slide = {"$wrapper": this.get_message(this.working_html()).appendTo(this.parent)}; @@ -506,7 +505,7 @@ var frappe_slides = [ icon: "fa fa-user", fields: [ { "fieldtype":"Attach Image", "fieldname":"attach_user_image", - label: __("Attach Your Picture"), is_private: 0}, + label: __("Attach Your Picture"), is_private: 0, align: 'center'}, { "fieldname": "full_name", "label": __("Full Name"), "fieldtype": "Data", reqd:1}, { "fieldname": "email", "label": __("Email Address") + ' (' + __("Will be your login ID") + ')', @@ -751,12 +750,4 @@ var utils = { frappe.setup.on("before_load", function() { // load slides frappe_slides.map(frappe.setup.add_slide); - - // set header image - let $icon = $('header .setup-wizard-brand'); - if($icon.length === 0) { - $('header').append(`- " style="color: #8d99a6; text-decoration: underline;" target="_blank">{unsubscribe_message}
@@ -474,6 +474,7 @@ def prepare_message(email, recipient, recipients_list): message = message.replace("", recipient) + message = (message and message.encode('utf8')) or '' if not email.attachments: return message diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index ed81d663d8..8d37745073 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -86,7 +86,7 @@ def backup_to_dropbox(): access_token = generate_oauth2_access_token_from_oauth1_token(dropbox_settings) if not access_token.get('oauth2_token'): - return + return 'Failed backup upload', 'No Access Token exists! Please generate the access token for Dropbox.' dropbox_settings['access_token'] = access_token['oauth2_token'] set_dropbox_access_token(access_token['oauth2_token']) diff --git a/frappe/integrations/doctype/gsuite_settings/gsuite_settings.py b/frappe/integrations/doctype/gsuite_settings/gsuite_settings.py index 9b6be3422d..29b1b64897 100644 --- a/frappe/integrations/doctype/gsuite_settings/gsuite_settings.py +++ b/frappe/integrations/doctype/gsuite_settings/gsuite_settings.py @@ -17,7 +17,7 @@ class GSuiteSettings(Document): def get_access_token(self): if not self.refresh_token: - raise UserError(_("Google GSuite is not configured.")) + raise frappe.ValidationError(_("Google GSuite is not configured.")) data = { 'client_id': self.client_id, 'client_secret': self.get_password(fieldname='client_secret',raise_exception=False), diff --git a/frappe/integrations/doctype/oauth_client/test_records.json b/frappe/integrations/doctype/oauth_client/test_records.json new file mode 100644 index 0000000000..904d959625 --- /dev/null +++ b/frappe/integrations/doctype/oauth_client/test_records.json @@ -0,0 +1,16 @@ +[ + { + "app_name": "_Test OAuth Client", + "client_id": "test_client_id", + "client_secret": "test_client_secret", + "default_redirect_uri": "http://localhost", + "docstatus": 0, + "doctype": "OAuth Client", + "grant_type": "Authorization Code", + "name": "test_client_id", + "redirect_uris": "http://localhost", + "response_type": "Code", + "scopes": "all openid", + "skip_authorization": 0 + } +] diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index a99a783e1f..01725e12a6 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -314,7 +314,7 @@ class DbTable: # if index key exists if frappe.db.sql("""show index from `{0}` where key_name=%s - and Non_unique=%s""".format(self.name), (col.fieldname, 0 if col.unique else 1)): + and Non_unique=%s""".format(self.name), (col.fieldname, col.unique)): query.append("drop index `{}`".format(col.fieldname)) for col in self.set_default: diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 2acb5b5db6..20ff7bb28f 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -206,6 +206,8 @@ def get_app_publisher(module): def make_boilerplate(template, doc, opts=None): target_path = get_doc_path(doc.module, doc.doctype, doc.name) template_name = template.replace("controller", scrub(doc.name)) + if template_name.endswith('._py'): + template_name = template_name[:-4] + '.py' target_file_path = os.path.join(target_path, template_name) if not doc: doc = {} diff --git a/frappe/public/build.json b/frappe/public/build.json index 3fafb6c5f2..b350c8151a 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -85,6 +85,7 @@ "public/js/frappe/dom.js", "public/js/frappe/ui/messages.js", "public/js/frappe/ui/keyboard.js", + "public/js/frappe/ui/emoji.js", "public/js/frappe/request.js", "public/js/frappe/socketio_client.js", @@ -132,6 +133,7 @@ "public/js/frappe/misc/help.js", "public/js/frappe/misc/help_links.js", "public/js/frappe/misc/address_and_contact.js", + "public/js/frappe/misc/preview_email.js", "public/js/frappe/ui/upload.html", "public/js/frappe/upload.js", @@ -163,6 +165,7 @@ "public/js/frappe/ui/charts.js", "public/js/frappe/ui/graph.js", + "public/js/frappe/ui/comment.js", "public/js/frappe/misc/rating_icons.html", "public/js/frappe/feedback.js" diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index 8ea2c5650f..bfce576e37 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -709,6 +709,21 @@ a.progress-small .progress-bar { .modal .note-editor [data-original-title="Table"] { display: none; } +.note-hint-popover { + border-radius: 3px; + border-color: #d1d8dd; + padding: 0; +} +.note-hint-popover .popover-content { + padding: 0; +} +.note-hint-popover .note-hint-item { + color: #36414C !important; + padding: 5px 8.8px !important; +} +.note-hint-popover .note-hint-item.active { + background-color: #F0F4F7 !important; +} .search-dialog .modal-dialog { width: 768px; } diff --git a/frappe/public/css/email.css b/frappe/public/css/email.css index 3f2df6d15f..57b2033d49 100644 --- a/frappe/public/css/email.css +++ b/frappe/public/css/email.css @@ -121,6 +121,9 @@ hr { .text-small { font-size: 10px; } +.text-bold { + font-weight: bold; +} .indicator { width: 8px; height: 8px; diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css index 844c2dc761..0d21271862 100644 --- a/frappe/public/css/form.css +++ b/frappe/public/css/form.css @@ -287,6 +287,8 @@ h6.uppercase, border-radius: 3px; margin-left: -7px; position: relative; + max-width: calc(100% - 50px); + padding-right: 0px; overflow: visible; } .timeline-item.user-content .avatar-medium { @@ -294,6 +296,11 @@ h6.uppercase, height: 45px; width: 45px; } +.timeline-item.user-content .action-btns { + position: absolute; + right: 0; + padding: 5px 15px 2px 5px; +} .timeline-item.user-content .comment-header { background-color: #fafbfc; padding: 10px 15px 10px 13px; @@ -301,12 +308,19 @@ h6.uppercase, color: #8D99A6; border-bottom: 1px solid #EBEFF2; } +.timeline-item.user-content .comment-header.links-active { + padding-right: 60px; +} +.timeline-item.user-content .comment-header .commented-on-small { + display: none; +} .timeline-item.user-content .comment-header .octicon-heart { color: #ff5858; cursor: pointer; } .timeline-item.user-content .reply { padding: 15px; + overflow: auto; } .timeline-item.user-content .reply > div > p:first-child { margin-top: 0px; @@ -317,11 +331,13 @@ h6.uppercase, .timeline-item.user-content .reply hr { margin: 10px 0px; } -.timeline-item.user-content .close-btn-container { - padding: 4px 10px 2px 5px; +.timeline-item.user-content .close-btn-container .close { + color: inherit; + opacity: 1; + padding: 0 0 0 10px; } .timeline-item.user-content .edit-btn-container { - padding: 4px 5px; + padding: 0; } .timeline-item.user-content .edit-btn-container .edit { color: inherit; @@ -515,6 +531,14 @@ h6.uppercase, padding: 0px; margin: 0px; } +.flex-justify-center { + display: flex; + justify-content: center; +} +.flex-justify-end { + display: flex; + justify-content: flex-end; +} .hide-control { display: none !important; } diff --git a/frappe/public/css/mobile.css b/frappe/public/css/mobile.css index 9e0bbe3597..ebcc52084f 100644 --- a/frappe/public/css/mobile.css +++ b/frappe/public/css/mobile.css @@ -192,6 +192,9 @@ body { } } @media (max-width: 767px) { + .toggle-sidebar { + margin-right: 0; + } body[data-route^="Form"] .page-title .title-text { font-size: 16px; width: calc(100% - 30px); @@ -331,4 +334,64 @@ body { body[data-route^="Form"] .page-head .sub-heading { right: 90px; } + .timeline::before { + content: none; + } + .timeline .timeline-new-email { + margin: 20px 0; + padding-left: 15px; + } + .timeline .timeline-new-email::before { + content: none; + } + .timeline .timeline-item.user-content { + margin: 20px 15px; + } + .timeline .timeline-item.user-content .media-body { + margin-left: 0; + max-width: 100%; + overflow: hidden; + } + .timeline .timeline-item.user-content .media-body:before { + content: none; + } + .timeline .timeline-item.user-content .action-btns { + padding: 5px 10px 2px 5px; + } + .timeline .timeline-item.user-content .comment-header { + padding: 7px 10px; + } + .timeline .timeline-item.user-content .comment-header .links-active { + padding-right: 10px; + } + .timeline .timeline-item.user-content .avatar-medium { + margin-right: 10px; + } + .timeline .timeline-item.user-content .reply { + padding: 10px; + } + .timeline .timeline-item.user-content .commented-on-small { + display: inline-block; + } + .timeline .timeline-item.user-content .commented-on-small { + display: inline-block; + } + .timeline .timeline-item.notification-content { + padding-left: 15px; + margin: 20px 0; + } + .timeline .timeline-item.notification-content::before { + content: none; + } + .timeline .timeline-item.notification-content .small { + padding-left: 0; + } + .timeline .timeline-item .delivery-status-indicator { + float: left; + margin: 0 5px 0 0; + } + .timeline .asset-details { + line-height: 24px; + /*Height of avtar image -36px to align text center vertically*/ + } } diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index aacdda239c..54bb4e0595 100755 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -318,7 +318,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ } else { $(me.input_area).toggle(false); if (me.disp_area) { - me.set_disp_area(); + me.set_disp_area(me.value); $(me.disp_area).toggle(true); } } @@ -332,8 +332,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ } }, - set_disp_area: function() { - let value = this.get_input_value(); + set_disp_area: function(value) { if(in_list(["Currency", "Int", "Float"], this.df.fieldtype) && (this.value === 0 || value === 0)) { // to set the 0 value in readonly for currency, int, float field @@ -449,7 +448,7 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ this.last_value = this.value; this.value = value; this.set_formatted_input(value); - this.set_disp_area(); + this.set_disp_area(value); this.set_mandatory && this.set_mandatory(value); }, set_formatted_input: function(value) { @@ -752,29 +751,38 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ if(!$.fn.datepicker.language[lang]) { lang = 'en'; } + this.today_text = __("Today"); this.datepicker_options = { language: lang, autoClose: true, - todayButton: new Date(), + todayButton: frappe.datetime.now_date(true), dateFormat: (frappe.boot.sysdefaults.date_format || 'yyyy-mm-dd'), - onSelect: function(dateStr) { - me.$input.trigger('change'); + startDate: frappe.datetime.now_date(true), + onSelect: () => { + this.$input.trigger('change'); }, - onShow: function() { - $('.datepicker--button:visible').text(__('Today')); + onShow: () => { + this.datepicker.$datepicker + .find('.datepicker--button:visible') + .text(me.today_text); - if(!me.frm) return; - var window_height = $(window).height(); - var window_scroll_top = $(window).scrollTop(); - var el_offset_top = me.$input.offset().top + 280; - var position = 'top left'; - if(window_height + window_scroll_top >= el_offset_top) { - position = 'bottom left'; - } - me.datepicker.update('position', position); + this.update_datepicker_position(); } }; }, + update_datepicker_position: function() { + if(!this.frm) return; + // show datepicker above or below the input + // based on scroll position + var window_height = $(window).height(); + var window_scroll_top = $(window).scrollTop(); + var el_offset_top = this.$input.offset().top + 280; + var position = 'top left'; + if(window_height + window_scroll_top >= el_offset_top) { + position = 'bottom left'; + } + this.datepicker.update('position', position); + }, set_datepicker: function() { this.$input.datepicker(this.datepicker_options); this.datepicker = this.$input.data('datepicker'); @@ -814,6 +822,30 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ } }); +frappe.ui.form.ControlDatetime = frappe.ui.form.ControlDate.extend({ + set_date_options: function() { + this._super(); + this.today_text = __("Now"); + $.extend(this.datepicker_options, { + timepicker: true, + timeFormat: "hh:ii:ss", + todayButton: frappe.datetime.now_datetime(true) + }); + }, + set_description: function() { + const { description } = this.df; + const { time_zone } = frappe.sys_defaults; + if (!frappe.datetime.is_timezone_same()) { + if (!description) { + this.df.description = time_zone; + } else if (!description.includes(time_zone)) { + this.df.description += '