diff --git a/frappe/boot.py b/frappe/boot.py index 37385ee7da..d5336fab92 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -48,6 +48,7 @@ def get_bootinfo(): bootinfo.letter_heads = get_letter_heads() bootinfo.active_domains = frappe.get_active_domains() bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")] + add_layouts(bootinfo) bootinfo.module_app = frappe.local.module_app bootinfo.single_types = [d.name for d in frappe.get_all('DocType', {'issingle': 1})] @@ -309,6 +310,10 @@ def get_additional_filters_from_hooks(): return filter_config +def add_layouts(bootinfo): + # add routes for readable doctypes + bootinfo.doctype_layouts = frappe.get_all('DocType Layout', ['name', 'route', 'document_type']) + def get_desk_settings(): role_list = frappe.get_all('Role', fields=['*'], filters=dict( name=['in', frappe.get_roles()] diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 47dec71fce..27e6543235 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -18,7 +18,7 @@ global_cache_keys = ("app_hooks", "installed_apps", 'scheduler_events', 'time_zone', 'webhooks', 'active_domains', 'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version', 'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts', - 'sitemap_routes', 'db_tables', 'doctype_name_map') + doctype_map_keys + 'sitemap_routes', 'db_tables') + doctype_map_keys user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang", "defaults", "user_permissions", "home_page", "linked_with", @@ -73,7 +73,7 @@ def clear_doctype_cache(doctype=None): if getattr(frappe.local, 'meta_cache') and (doctype in frappe.local.meta_cache): del frappe.local.meta_cache[doctype] - for key in ('is_table', 'doctype_modules', 'doctype_name_map', 'document_cache'): + for key in ('is_table', 'doctype_modules', 'document_cache'): cache.delete_value(key) frappe.local.document_cache = {} diff --git a/frappe/core/doctype/data_import_legacy/data_import_legacy.js b/frappe/core/doctype/data_import_legacy/data_import_legacy.js index 9a301af76e..8e4f397171 100644 --- a/frappe/core/doctype/data_import_legacy/data_import_legacy.js +++ b/frappe/core/doctype/data_import_legacy/data_import_legacy.js @@ -32,7 +32,7 @@ frappe.ui.form.on('Data Import Legacy', { frm.reload_doc(); } if (data.progress) { - let progress_bar = $(frm.dashboard.progress_area).find(".progress-bar"); + let progress_bar = $(frm.dashboard.progress_area.body).find(".progress-bar"); if (progress_bar) { $(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped"); $(progress_bar).css("width", data.progress + "%"); diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 215ef8cd62..569414e98b 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -132,7 +132,7 @@ "label": "Editable Grid" }, { - "default": "1", + "default": "0", "depends_on": "eval:!doc.istable && !doc.issingle", "description": "Open a dialog with mandatory fields to create a new record quickly", "fieldname": "quick_entry", @@ -427,7 +427,7 @@ "label": "Allow Guest to View" }, { - "depends_on": "has_web_view", + "depends_on": "eval:!doc.istable", "fieldname": "route", "fieldtype": "Data", "label": "Route" @@ -609,7 +609,7 @@ "link_fieldname": "reference_doctype" } ], - "modified": "2020-09-24 13:13:58.227153", + "modified": "2020-12-10 15:10:09.227205", "modified_by": "Administrator", "module": "Core", "name": "DocType", @@ -637,6 +637,7 @@ "write": 1 } ], + "route": "doctype", "search_fields": "module", "show_name_in_global_search": 1, "sort_field": "modified", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index cce5968f9c..7c0c270277 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -26,6 +26,7 @@ from frappe.database.schema import validate_column_name, validate_column_length from frappe.model.docfield import supports_translation from frappe.modules.import_file import get_file_path from frappe.model.meta import Meta +from frappe.desk.utils import get_doctype_route class InvalidFieldNameError(frappe.ValidationError): pass @@ -63,15 +64,7 @@ class DocType(Document): self.validate_name() - if self.issingle: - self.allow_import = 0 - self.is_submittable = 0 - self.istable = 0 - - elif self.istable: - self.allow_import = 0 - self.permissions = [] - + self.set_defaults_for_single_and_table() self.scrub_field_names() self.set_default_in_list_view() self.set_default_translatable() @@ -79,10 +72,7 @@ class DocType(Document): self.validate_document_type() validate_fields(self) - if self.istable: - # no permission records for child table - self.permissions = [] - else: + if not self.istable: validate_permissions(self) self.make_amendable() @@ -93,8 +83,6 @@ class DocType(Document): if not self.is_new(): self.before_update = frappe.get_doc('DocType', self.name) - - if not self.is_new(): self.setup_fields_to_fetch() check_email_append_to(self) @@ -102,14 +90,20 @@ class DocType(Document): if self.default_print_format and not self.custom: frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) - if frappe.conf.get('developer_mode'): - self.owner = 'Administrator' - self.modified_by = 'Administrator' - def after_insert(self): # clear user cache so that on the next reload this doctype is included in boot clear_user_cache(frappe.session.user) + def set_defaults_for_single_and_table(self): + if self.issingle: + self.allow_import = 0 + self.is_submittable = 0 + self.istable = 0 + + elif self.istable: + self.allow_import = 0 + self.permissions = [] + def set_default_in_list_view(self): '''Set default in-list-view for first 4 mandatory fields''' if not [d.fieldname for d in self.fields if d.in_list_view]: @@ -134,6 +128,10 @@ class DocType(Document): if not frappe.conf.get("developer_mode") and not self.custom: frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError) + if frappe.conf.get('developer_mode'): + self.owner = 'Administrator' + self.modified_by = 'Administrator' + def setup_fields_to_fetch(self): '''Setup query to update values for newly set fetch values''' try: @@ -192,6 +190,12 @@ class DocType(Document): def validate_website(self): """Ensure that website generator has field 'route'""" + if not self.istable and not self.route: + self.route = get_doctype_route(self.name) + + if self.route: + self.route = self.route.strip('/') + if self.has_web_view: # route field must be present if not 'route' in [d.fieldname for d in self.fields]: diff --git a/frappe/core/doctype/doctype/patches/set_route.py b/frappe/core/doctype/doctype/patches/set_route.py new file mode 100644 index 0000000000..655935f861 --- /dev/null +++ b/frappe/core/doctype/doctype/patches/set_route.py @@ -0,0 +1,7 @@ +import frappe +from frappe.desk.utils import get_doctype_route + +def execute(): + for doctype in frappe.get_all('DocType', ['name', 'route'], dict(istable=0)): + if not doctype.route: + frappe.db.set_value('DocType', doctype.name, 'route', get_doctype_route(doctype.name), update_modified = False) \ No newline at end of file diff --git a/frappe/core/doctype/navbar_settings/navbar_settings.py b/frappe/core/doctype/navbar_settings/navbar_settings.py index f7c437bf00..db510981a4 100644 --- a/frappe/core/doctype/navbar_settings/navbar_settings.py +++ b/frappe/core/doctype/navbar_settings/navbar_settings.py @@ -23,7 +23,7 @@ class NavbarSettings(Document): if not frappe.flags.in_patch and (len(before_save_items) > len(after_save_items)): frappe.throw(_("Please hide the standard navbar items instead of deleting them")) -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def get_app_logo(): app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo') if not app_logo: diff --git a/frappe/core/doctype/role/role.json b/frappe/core/doctype/role/role.json index 1e2366c041..e47dc7194b 100644 --- a/frappe/core/doctype/role/role.json +++ b/frappe/core/doctype/role/role.json @@ -16,7 +16,7 @@ "two_factor_auth", "navigation_settings_section", "search_bar", - "notification", + "notifications", "chat", "list_settings_section", "list_sidebar", @@ -84,12 +84,6 @@ "fieldtype": "Check", "label": "Search Bar" }, - { - "default": "1", - "fieldname": "notification", - "fieldtype": "Check", - "label": "Notification" - }, { "default": "1", "fieldname": "chat", @@ -141,13 +135,19 @@ "fieldname": "view_switcher", "fieldtype": "Check", "label": "View Switcher" + }, + { + "default": "1", + "fieldname": "notifications", + "fieldtype": "Check", + "label": "Notifications" } ], "icon": "fa fa-bookmark", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-11-11 17:29:13.149522", + "modified": "2020-12-03 14:08:38.181035", "modified_by": "Administrator", "module": "Core", "name": "Role", diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py index f9fbd9cbe6..bac68e30ab 100644 --- a/frappe/core/doctype/role/role.py +++ b/frappe/core/doctype/role/role.py @@ -6,7 +6,7 @@ import frappe from frappe.model.document import Document -desk_properties = ("search_bar", "notification", "chat", "list_sidebar", +desk_properties = ("search_bar", "notifications", "chat", "list_sidebar", "bulk_actions", "view_switcher", "form_sidebar", "timeline", "dashboard") class Role(Document): diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index ba14583c2f..de14651d50 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -55,7 +55,7 @@ class UserPermission(Document): ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name) frappe.throw(_("{0} has already assigned default value for {1}.").format(ref_link, self.allow)) -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def get_user_permissions(user=None): '''Get all users permissions for the user as a dict of doctype''' # if this is called from client-side, @@ -66,7 +66,7 @@ def get_user_permissions(user=None): if not user: user = frappe.session.user - if not user or user == "Administrator": + if not user or user in ("Administrator", "Guest"): return {} cached_user_permissions = frappe.cache().hget("user_permissions", user) diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.json b/frappe/custom/doctype/doctype_layout/doctype_layout.json index 420ce09a99..e47c9e03e0 100644 --- a/frappe/custom/doctype/doctype_layout/doctype_layout.json +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.json @@ -8,6 +8,7 @@ "engine": "InnoDB", "field_order": [ "document_type", + "route", "fields", "client_script" ], @@ -31,11 +32,17 @@ "fieldname": "client_script", "fieldtype": "Code", "label": "Client Script" + }, + { + "fieldname": "route", + "fieldtype": "Data", + "label": "Route", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-11-17 15:49:49.669291", + "modified": "2020-12-10 15:01:04.352184", "modified_by": "Administrator", "module": "Custom", "name": "DocType Layout", @@ -52,8 +59,13 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "read": 1, + "role": "Guest" } ], + "route": "doctype-layout", "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.py b/frappe/custom/doctype/doctype_layout/doctype_layout.py index a8385eaa18..b580ac8f56 100644 --- a/frappe/custom/doctype/doctype_layout/doctype_layout.py +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.py @@ -7,6 +7,9 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe.desk.utils import get_doctype_route + class DocTypeLayout(Document): def validate(self): - frappe.cache().delete_value('doctype_name_map') + if not self.route: + self.route = get_doctype_route(self.name) diff --git a/frappe/custom/doctype/doctype_layout/patches/convert_web_forms_to_doctype_layout.py b/frappe/custom/doctype/doctype_layout/patches/convert_web_forms_to_doctype_layout.py new file mode 100644 index 0000000000..4e44743b48 --- /dev/null +++ b/frappe/custom/doctype/doctype_layout/patches/convert_web_forms_to_doctype_layout.py @@ -0,0 +1,13 @@ +import frappe + +def execute(): + for web_form_name in frappe.db.get_all('Web Form', pluck='name'): + web_form = frappe.get_doc('Web Form', web_form_name) + doctype_layout = frappe.get_doc(dict( + doctype = 'DocType Layout', + document_type = web_form.doc_type, + name = web_form.title, + route = web_form.route, + fields = [dict(fieldname = d.fieldname, label=d.label) for d in web_form.web_form_fields if d.fieldname] + )).insert() + print(doctype_layout.name) \ No newline at end of file diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 2d9223edf8..1f5c437330 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -13,7 +13,7 @@ from frappe.desk.form.document_follow import is_document_followed from frappe import _ from six.moves.urllib.parse import quote -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def getdoc(doctype, name, user=None): """ Loads a doclist for a given document. This method is called directly from the client. @@ -52,7 +52,7 @@ def getdoc(doctype, name, user=None): frappe.response.docs.append(doc) -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def getdoctype(doctype, with_parent=False, cached_timestamp=None): """load doctype""" diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index c28a40657f..d5428b1da2 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -202,13 +202,17 @@ class FormMeta(Meta): self.load_kanban_column_fields() def load_kanban_column_fields(self): - values = frappe.get_list( - 'Kanban Board', fields=['field_name'], - filters={'reference_doctype': self.name}) + try: + values = frappe.get_list( + 'Kanban Board', fields=['field_name'], + filters={'reference_doctype': self.name}) - fields = [x['field_name'] for x in values] - fields = list(set(fields)) - self.set("__kanban_column_fields", fields, as_value=True) + fields = [x['field_name'] for x in values] + fields = list(set(fields)) + self.set("__kanban_column_fields", fields, as_value=True) + except frappe.PermissionError: + # no access to kanban board + pass def get_code_files_via_hooks(hook, name): code_files = [] diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py index 1d10a13930..91dc0f3ba9 100644 --- a/frappe/desk/listview.py +++ b/frappe/desk/listview.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def get_list_settings(doctype): try: return frappe.get_cached_doc("List View Settings", doctype) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 9f5a5d84c8..6cd1d24626 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -14,7 +14,7 @@ from frappe.core.doctype.access_log.access_log import make_access_log from frappe.utils import cstr, format_duration -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) @frappe.read_only() def get(): args = get_form_params() diff --git a/frappe/desk/utils.py b/frappe/desk/utils.py index 99318bd54b..a3de00cd54 100644 --- a/frappe/desk/utils.py +++ b/frappe/desk/utils.py @@ -3,26 +3,5 @@ import frappe -@frappe.whitelist(allow_guest=True) -def get_doctype_name(name): - # translates the doctype name from url to name `sales-order` to `Sales Order` - # also supports document type layouts - # if with_layout is set: return the layout object too - - def get_name_map(): - name_map = {} - for d in frappe.get_all('DocType'): - name_map[d.name.lower().replace(' ', '-')] = frappe._dict(doctype = d.name) - - for d in frappe.get_all('DocType Layout', fields = ['name', 'document_type']): - name_map[d.name.lower().replace(' ', '-')] = frappe._dict(doctype = d.document_type, doctype_layout = d.name) - - return name_map - - data = frappe._dict(name_map = frappe.cache().get_value('doctype_name_map', get_name_map).get(name, dict(doctype = name))) - - if data.name_map.get('doctype_layout'): - # return the layout object - frappe.response.docs.append(frappe.get_doc('DocType Layout', data.name_map.get('doctype_layout')).as_dict()) - - return data \ No newline at end of file +def get_doctype_route(name): + return name.lower().replace(' ', '-') \ No newline at end of file diff --git a/frappe/public/icons/timeless/symbol-defs.svg b/frappe/public/icons/timeless/symbol-defs.svg index e11c26aa29..7d5fb68c7e 100644 --- a/frappe/public/icons/timeless/symbol-defs.svg +++ b/frappe/public/icons/timeless/symbol-defs.svg @@ -35,13 +35,9 @@ - - - - - - - + + + @@ -660,4 +656,12 @@ + + + + + + + + diff --git a/frappe/public/js/frappe/chat.js b/frappe/public/js/frappe/chat.js index e17a7ebb4d..61fa89deab 100644 --- a/frappe/public/js/frappe/chat.js +++ b/frappe/public/js/frappe/chat.js @@ -2670,20 +2670,6 @@ frappe.chat.render = (render = true, force = false) => // With the assumption, that there's only one navbar. const $placeholder = $('.navbar .frappe-chat-dropdown') - // Render if frappe-chat-toggle doesn't exist. - if ( frappe.utils.is_empty($placeholder.has('.frappe-chat-toggle')) ) { - const $template = $(` - -
- -
-
- `) - - $placeholder.addClass('dropdown hidden') - $placeholder.html($template) - } - if ( render ) { $placeholder.removeClass('hidden') } else { diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 5a6ea54a3c..f80689ed10 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -160,8 +160,6 @@ frappe.Application = Class.extend({ }, 600000); // check every 10 minutes } } - - this.fetch_tags(); }, set_route() { @@ -170,7 +168,7 @@ frappe.Application = Class.extend({ localStorage.removeItem("session_last_route"); } else { // route to home page - frappe.route(); + frappe.router.route(); } }, @@ -263,6 +261,7 @@ frappe.Application = Class.extend({ this.check_metadata_cache_status(); this.set_globals(); this.sync_pages(); + frappe.router.setup(); moment.locale("en"); moment.user_utc_offset = moment().utcOffset(); if(frappe.boot.timezone_info) { @@ -272,6 +271,7 @@ frappe.Application = Class.extend({ frappe.dom.set_style(frappe.boot.print_css, "print-style"); } frappe.user.name = frappe.boot.user.name; + frappe.router.setup(); } else { this.set_as_guest(); } @@ -294,6 +294,7 @@ frappe.Application = Class.extend({ set_globals: function() { frappe.session.user = frappe.boot.user.name; + frappe.session.logged_in_user = frappe.boot.user.name; frappe.session.user_email = frappe.boot.user.email; frappe.session.user_fullname = frappe.user_info().fullname; @@ -599,10 +600,6 @@ frappe.Application = Class.extend({ frappe.show_alert(message); }); }, - - fetch_tags() { - frappe.tags.utils.fetch_tags(); - } }); frappe.get_module = function(m, default_module) { diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 3737cd555b..ce1e2ae79d 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -6,6 +6,8 @@ // add_fetches import Awesomplete from 'awesomplete'; +frappe.ui.form.recent_link_validations = {}; + frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ make_input: function() { var me = this; @@ -439,40 +441,44 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ this.docname, value); }, validate_link_and_fetch: function(df, doctype, docname, value) { - var me = this; - if(value) { return new Promise((resolve) => { var fetch = ''; - if(this.frm && this.frm.fetch_dict[df.fieldname]) { fetch = this.frm.fetch_dict[df.fieldname].columns.join(', '); } - return frappe.call({ - method:'frappe.desk.form.utils.validate_link', - type: "GET", - args: { - 'value': value, - 'options': doctype, - 'fetch': fetch - }, - no_spinner: true, - callback: function(r) { - if(r.message=='Ok') { - if(r.fetch_values && docname) { - me.set_fetch_values(df, docname, r.fetch_values); - } - resolve(r.valid_value); - } else { - resolve(""); - } - } - }); + // if default and no fetch, no need to validate + if (!fetch && df.__default_value && df.__default_value===value) return value; + + this.fetch_and_validate_link(resolve, df, doctype, docname, value, fetch); }); } }, + fetch_and_validate_link(resolve, df, doctype, docname, value, fetch) { + frappe.call({ + method:'frappe.desk.form.utils.validate_link', + type: "GET", + args: { + 'value': value, + 'options': doctype, + 'fetch': fetch + }, + no_spinner: true, + callback: (r) => { + if(r.message=='Ok') { + if(r.fetch_values && docname) { + this.set_fetch_values(df, docname, r.fetch_values); + } + resolve(r.valid_value); + } else { + resolve(""); + } + } + }); + }, + set_fetch_values: function(df, docname, fetch_values) { var fl = this.frm.fetch_dict[df.fieldname].fields; for(var i=0; i < fl.length; i++) { diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index c9b5ab542c..4d9fee9faf 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -1,92 +1,101 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt -frappe.ui.form.Dashboard = Class.extend({ - init: function(opts) { +frappe.ui.form.Dashboard = class FormDashboard { + constructor(opts) { $.extend(this, opts); - this.section = this.frm.fields_dict._form_dashboard.wrapper; - this.parent = this.section.find('.section-body'); - this.wrapper = $(frappe.render_template('form_dashboard', - {frm: this.frm})).appendTo(this.parent); + this.setup_dashboard_sections(); + } - this.progress_area = this.wrapper.find(".progress-area"); - this.heatmap_area = this.wrapper.find('.form-heatmap'); - this.chart_area = this.wrapper.find('.form-graph'); - this.stats_area = this.wrapper.find('.form-stats'); - this.stats_area_row = this.stats_area.find('.row'); - this.links_area = this.wrapper.find('.form-links'); - this.transactions_area = this.links_area.find('.transactions'); + setup_dashboard_sections() { + this.progress_area = new Section(this.parent, { + css_class: 'progress-area', + hidden: 1, + collapsible: 1 + }); - }, - reset: function() { + this.heatmap_area = new Section(this.parent, { + title: __("Overview"), + css_class: 'form-heatmap', + hidden: 1, + collapsible: 1, + body_html: ` +
+ + ` + }); + + this.chart_area = new Section(this.parent, { + title: __("Graph"), + css_class: 'form-graph', + hidden: 1, + collapsible: 1 + }); + + this.stats_area_row = $(`
`); + this.stats_area = new Section(this.parent, { + title: __("Stats"), + css_class: 'form-stats', + hidden: 1, + collapsible: 1, + body_html: this.stats_area_row + }); + + this.transactions_area = $(`
${text}
`, color); - } else { - this.clear_headline(); - } - }, - - add_section: function(html, section_head=null) { - let section = $(`
`); - if (section_head) { - section.append(`
${section_head}
`); - } - section.append(html); - section.appendTo(this.wrapper); - return section; - }, - - add_progress: function(title, percent, message) { - var progress_chart = this.make_progress_chart(title); - - if(!$.isArray(percent)) { + if (!$.isArray(percent)) { percent = this.format_percent(title, percent); } - var progress = $('
').appendTo(progress_chart); + let progress = $('
').appendTo(progress_chart); + $.each(percent, function(i, opts) { - $(repl('
', opts)).appendTo(progress); + $(`
`).appendTo(progress); }); if (!message) message = ''; @@ -95,9 +104,9 @@ frappe.ui.form.Dashboard = Class.extend({ this.show(); return progress_chart; - }, + } - show_progress: function(title, percent, message) { + show_progress(title, percent, message) { this._progress_map = this._progress_map || {}; let progress_chart = this._progress_map[title]; // create a new progress chart if it doesnt exist @@ -119,19 +128,19 @@ frappe.ui.form.Dashboard = Class.extend({ if (!message) message = ''; progress_chart.find('.progress-message').text(message); - }, + } - hide_progress: function(title) { - if (title){ + hide_progress(title) { + if (title) { this._progress_map[title].remove(); delete this._progress_map[title]; } else { this._progress_map = {}; - this.progress_area.empty(); + this.progress_area.hide(); } - }, + } - format_percent: function(title, percent) { + format_percent(title, percent) { const percentage = cint(percent); const width = percentage < 0 ? 100 : percentage; const progress_class = percentage < 0 ? "progress-bar-danger" : "progress-bar-success"; @@ -141,28 +150,30 @@ frappe.ui.form.Dashboard = Class.extend({ width: width + '%', progress_class: progress_class }]; - }, - make_progress_chart: function(title) { - var progress_chart = $('
') - .appendTo(this.progress_area.removeClass('hidden')); - return progress_chart; - }, + } - refresh: function() { + make_progress_chart(title) { + this.progress_area.show(); + var progress_chart = $('
') + .appendTo(this.progress_area.body); + return progress_chart; + } + + refresh() { this.reset(); - if(this.frm.doc.__islocal) { + if (this.frm.doc.__islocal || !frappe.boot.desk_settings.form_dashboard) { return; } - if(!this.data) { + if (!this.data) { this.init_data(); } var show = false; - if(this.data && ((this.data.transactions || []).length + if (this.data && ((this.data.transactions || []).length || (this.data.reports || []).length)) { - if(this.data.docstatus && this.frm.doc.docstatus !== this.data.docstatus) { + if (this.data.docstatus && this.frm.doc.docstatus !== this.data.docstatus) { // limited docstatus return; } @@ -171,53 +182,53 @@ frappe.ui.form.Dashboard = Class.extend({ show = true; } - if(this.data.heatmap) { + if (this.data.heatmap) { this.render_heatmap(); show = true; } - if(this.data.graph) { + if (this.data.graph) { this.setup_graph(); // show = true; } - if(show) { + if (show) { this.show(); } - }, + } - after_refresh: function() { + after_refresh() { var me = this; // show / hide new buttons (if allowed) - this.links_area.find('.btn-new').each(function() { - if(me.frm.can_create($(this).attr('data-doctype'))) { + this.links_area.body.find('.btn-new').each(function() { + if (me.frm.can_create($(this).attr('data-doctype'))) { $(this).removeClass('hidden'); } }); - }, + } - init_data: function() { + init_data() { this.data = this.frm.meta.__dashboard || {}; - if(!this.data.transactions) this.data.transactions = []; - if(!this.data.internal_links) this.data.internal_links = {}; + if (!this.data.transactions) this.data.transactions = []; + if (!this.data.internal_links) this.data.internal_links = {}; this.filter_permissions(); - }, + } - add_transactions: function(opts) { + add_transactions(opts) { // add additional data on dashboard let group_added = []; - if(!Array.isArray(opts)) opts=[opts]; + if (!Array.isArray(opts)) opts=[opts]; - if(!this.data) { + if (!this.data) { this.init_data(); } - if(this.data && (this.data.transactions || []).length) { + if (this.data && (this.data.transactions || []).length) { // check if label already exists, add items to it this.data.transactions.map(group => { opts.map(d => { - if(d.label == group.label) { + if (d.label == group.label) { group_added.push(d.label); group.items.push(...d.items); } @@ -226,80 +237,81 @@ frappe.ui.form.Dashboard = Class.extend({ // if label not already present, add new label and items under it opts.map(d => { - if(!group_added.includes(d.label)) { + if (!group_added.includes(d.label)) { this.data.transactions.push(d); } }); this.filter_permissions(); } - }, + } - filter_permissions: function() { + filter_permissions() { // filter out transactions for which the user // does not have permission - var transactions = []; + let transactions = []; (this.data.transactions || []).forEach(function(group) { - var items = []; + let items = []; group.items.forEach(function(doctype) { - if(frappe.model.can_read(doctype)) { + if (frappe.model.can_read(doctype)) { items.push(doctype); } }); - // only add thie group, if there is atleast + // only add this group, if there is at-least // one item with permission - if(items.length) { + if (items.length) { group.items = items; transactions.push(group); } }); this.data.transactions = transactions; - }, - render_links: function() { + } + + render_links() { var me = this; - this.links_area.removeClass('hidden'); - this.links_area.find('.btn-new').addClass('hidden'); - if(this.data_rendered) { + this.links_area.show(); + this.links_area.body.find('.btn-new').addClass('hidden'); + if (this.data_rendered) { return; } - //this.transactions_area.empty(); - this.data.frm = this.frm; + let transactions_area_body = this.transactions_area; + $(frappe.render_template('form_links', this.data)) - .appendTo(this.transactions_area) + .appendTo(transactions_area_body); if (this.data.reports && this.data.reports.length) { $(frappe.render_template('report_links', this.data)) - .appendTo(this.transactions_area) + .appendTo(transactions_area_body); } // bind links - this.transactions_area.find(".badge-link").on('click', function() { + transactions_area_body.find(".badge-link").on('click', function() { me.open_document_list($(this).parent()); }); // bind reports - this.transactions_area.find(".report-link").on('click', function() { + transactions_area_body.find(".report-link").on('click', function() { me.open_report($(this).parent()); }); // bind open notifications - this.transactions_area.find('.open-notification').on('click', function() { + transactions_area_body.find('.open-notification').on('click', function() { me.open_document_list($(this).parent(), true); }); // bind new - this.transactions_area.find('.btn-new').on('click', function() { + transactions_area_body.find('.btn-new').on('click', function() { me.frm.make_new($(this).attr('data-doctype')); }); this.data_rendered = true; - }, - open_report: function($link) { + } + open_report($link) { let report = $link.attr('data-report'); let fieldname = this.data.non_standard_fieldnames @@ -308,28 +320,30 @@ frappe.ui.form.Dashboard = Class.extend({ frappe.route_options[fieldname] = this.frm.doc.name; frappe.set_route("query-report", report); - }, - open_document_list: function($link, show_open) { + } + + open_document_list($link, show_open) { // show document list with filters var doctype = $link.attr('data-doctype'), names = $link.attr('data-names') || []; - if(this.data.internal_links[doctype]) { - if(names.length) { + if (this.data.internal_links[doctype]) { + if (names.length) { frappe.route_options = {'name': ['in', names]}; } else { return false; } - } else if(this.data.fieldname) { + } else if (this.data.fieldname) { frappe.route_options = this.get_document_filter(doctype); - if(show_open) { + if (show_open) { frappe.ui.notifications.show_open_count_list(doctype); } } frappe.set_route("List", doctype, "List"); - }, - get_document_filter: function(doctype) { + } + + get_document_filter(doctype) { // return the default filter for the given document // like {"customer": frm.doc.name} var filter = {}; @@ -344,9 +358,10 @@ frappe.ui.form.Dashboard = Class.extend({ filter[fieldname] = this.frm.doc.name; return filter; - }, - set_open_count: function() { - if(!this.data.transactions || !this.data.fieldname) { + } + + set_open_count() { + if (!this.data.transactions || !this.data.fieldname) { return; } @@ -355,7 +370,9 @@ frappe.ui.form.Dashboard = Class.extend({ me = this; this.data.transactions.forEach(function(group) { - group.items.forEach(function(item) { items.push(item); }); + group.items.forEach(function(item) { + items.push(item); + }); }); var method = this.data.method || 'frappe.desk.notifications.get_open_count'; @@ -368,7 +385,7 @@ frappe.ui.form.Dashboard = Class.extend({ items: items }, callback: function(r) { - if(r.message.timeline_data) { + if (r.message.timeline_data) { me.update_heatmap(r.message.timeline_data); } @@ -404,12 +421,13 @@ frappe.ui.form.Dashboard = Class.extend({ } }); - }, - set_badge_count: function(doctype, open_count, count, names) { + } + + set_badge_count(doctype, open_count, count, names) { var $link = $(this.transactions_area) .find('.document-link[data-doctype="'+doctype+'"]'); - if(open_count) { + if (open_count) { $link.find('.open-notification') .removeClass('hidden') .html((open_count > 99) ? '99+' : open_count); @@ -421,24 +439,24 @@ frappe.ui.form.Dashboard = Class.extend({ .text((count > 99) ? '99+' : count); } - if(this.data.internal_links[doctype]) { - if(names && names.length) { + if (this.data.internal_links[doctype]) { + if (names && names.length) { $link.attr('data-names', names ? names.join(',') : ''); } else { $link.find('a').attr('disabled', true); } } - }, + } - update_heatmap: function(data) { - if(this.heatmap) { + update_heatmap(data) { + if (this.heatmap) { this.heatmap.update({dataPoints: data}); } - }, + } // heatmap - render_heatmap: function() { - if(!this.heatmap) { + render_heatmap() { + if (!this.heatmap) { this.heatmap = new frappe.Chart("#heatmap-" + frappe.model.scrub(this.frm.doctype), { type: 'heatmap', start: new Date(moment().subtract(1, 'year').toDate()), @@ -449,32 +467,36 @@ frappe.ui.form.Dashboard = Class.extend({ }); // center the heatmap - this.heatmap_area.removeClass('hidden').find('svg').css({'margin': 'auto'}); + this.heatmap_area.show(); + this.heatmap_area.body.find('svg').css({'margin': 'auto'}); // message - var heatmap_message = this.heatmap_area.find('.heatmap-message'); - if(this.data.heatmap_message) { + var heatmap_message = this.heatmap_area.body.find('.heatmap-message'); + if (this.data.heatmap_message) { heatmap_message.removeClass('hidden').html(this.data.heatmap_message); } else { heatmap_message.addClass('hidden'); } } - }, + } - add_indicator: function(label, color) { + add_indicator(label, color) { this.show(); - this.stats_area.removeClass('hidden'); + this.stats_area.show(); // set colspan var indicators = this.stats_area_row.find('.indicator-column'); var n_indicators = indicators.length + 1; var colspan; - if(n_indicators > 4) { colspan = 3 } - else { colspan = 12 / n_indicators; } + if (n_indicators > 4) { + colspan = 3; + } else { + colspan = 12 / n_indicators; + } // reset classes in existing indicators - if(indicators.length) { + if (indicators.length) { indicators.removeClass().addClass('col-sm-'+colspan).addClass('indicator-column'); } @@ -482,10 +504,10 @@ frappe.ui.form.Dashboard = Class.extend({ +label+'').appendTo(this.stats_area_row); return indicator; - }, + } // graphs - setup_graph: function() { + setup_graph() { var me = this; var method = this.data.graph_method; var args = { @@ -500,7 +522,7 @@ frappe.ui.form.Dashboard = Class.extend({ args: args, callback: function(r) { - if(r.message) { + if (r.message) { me.render_graph(r.message); me.show(); } else { @@ -508,11 +530,11 @@ frappe.ui.form.Dashboard = Class.extend({ } } }); - }, + } - render_graph: function(args) { - var me = this; - this.chart_area.empty().removeClass('hidden'); + render_graph(args) { + this.chart_area.show(); + this.chart_area.body.empty(); $.extend(args, { type: 'line', colors: ['green'], @@ -524,21 +546,151 @@ frappe.ui.form.Dashboard = Class.extend({ this.show(); this.chart = new frappe.Chart('.form-graph', args); - if(!this.chart) { + if (!this.chart) { this.hide(); } - }, + } - show: function() { + show() { this.toggle_visibility(true); - }, + } - hide: function() { + hide() { this.toggle_visibility(false); - }, + } toggle_visibility(show) { - this.section.toggleClass('visible-section', show); - this.section.toggleClass('empty-section', !show); + this.parent.toggleClass('visible-section', show); + this.parent.toggleClass('empty-section', !show); } -}); + + // TODO: Review! code related to headline should be the part of layout/form + set_headline(html, color) { + this.frm.layout.show_message(html, color); + } + + clear_headline() { + this.frm.layout.show_message(); + } + + add_comment(text, alert_class, permanent) { + var me = this; + this.set_headline_alert(text, alert_class); + if (!permanent) { + setTimeout(function() { + me.clear_headline(); + }, 10000); + } + } + + clear_comment() { + this.clear_headline(); + } + + set_headline_alert(text, color) { + if (text) { + this.set_headline(`
${text}
`, color); + } else { + this.clear_headline(); + } + } +}; + +class Section { + constructor(parent, options) { + this.parent = parent; + this.df = options || {}; + this.make(); + + if (this.df.title && this.df.collapsible) { + this.collapse(); + } + this.refresh(); + } + + make() { + this.wrapper = $(`
`) + .appendTo(this.parent); + + if (this.df) { + if (this.df.title) { + this.make_head(); + } + if (this.df.description) { + this.description_wrapper = $( + `
+ ${__(this.df.description)} +
` + ); + + this.wrapper.append(this.description_wrapper); + } + if (this.df.css_class) { + this.wrapper.addClass(this.df.css_class); + } + if (this.df.hide_border) { + this.wrapper.toggleClass("hide-border", true); + } + } + + this.body = $('
').appendTo(this.wrapper); + + if (this.df.body_html) { + this.body.append(this.df.body_html); + } + } + + make_head() { + this.head = $(` +
+ ${__(this.df.title)} + +
+ `); + + this.head.appendTo(this.wrapper); + this.indicator = this.head.find('.collapse-indicator'); + this.indicator.hide(); + + if (this.df.collapsible) { + // show / hide based on status + this.collapse_link = this.head.on("click", () => { + this.collapse(); + }); + this.indicator.show(); + } + } + + refresh() { + if (!this.df) return; + + // hide if explicitly hidden + let hide = this.df.hidden; + this.wrapper.toggle(!hide); + } + + collapse(hide) { + if (hide === undefined) { + hide = !this.body.hasClass("hide"); + } + + this.body.toggleClass("hide", hide); + this.head && this.head.toggleClass("collapsed", hide); + + let indicator_icon = hide ? 'down' : 'up-line'; + + this.indicator & this.indicator.html(frappe.utils.icon(indicator_icon, 'sm', 'mb-1')); + } + + is_collapsed() { + return this.body.hasClass('hide'); + } + + hide() { + this.wrapper.hide(); + } + + show() { + this.wrapper.show(); + } +} \ No newline at end of file diff --git a/frappe/public/js/frappe/form/footer/footer.js b/frappe/public/js/frappe/form/footer/footer.js index 827eb861dc..074646c67b 100644 --- a/frappe/public/js/frappe/form/footer/footer.js +++ b/frappe/public/js/frappe/form/footer/footer.js @@ -48,7 +48,7 @@ frappe.ui.form.Footer = Class.extend({ }); }, get_names_for_mentions() { - let names_for_mentions = Object.keys(frappe.boot.user_info) + let names_for_mentions = Object.keys(frappe.boot.user_info || []) .filter(user => { return !["Administrator", "Guest"].includes(user) && frappe.boot.user_info[user].allowed_in_mentions; diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index efe86ee0f3..8f44b38343 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1,4 +1,5 @@ frappe.provide('frappe.ui.form'); +frappe.provide('frappe.model.docinfo'); import './quick_entry'; import './toolbar'; @@ -23,13 +24,10 @@ frappe.ui.form.Form = class FrappeForm { this.docname = ''; this.doctype = doctype; this.doctype_layout_name = doctype_layout_name; - if (doctype_layout_name) { - this.doctype_layout = frappe.get_doc('DocType Layout', doctype_layout_name); - } + this.in_form = in_form ? true : false; + this.hidden = false; this.refresh_if_stale_for = 120; - - var me = this; this.opendocs = {}; this.custom_buttons = {}; this.sections = []; @@ -39,17 +37,8 @@ frappe.ui.form.Form = class FrappeForm { this.pformat = {}; this.fetch_dict = {}; this.parent = parent; - + this.doctype_layout = frappe.get_doc('DocType Layout', doctype_layout_name); this.setup_meta(doctype); - - // show in form instead of in dialog, when called using url (router.js) - this.in_form = in_form ? true : false; - - // notify on rename - $(document).on('rename', function(event, dt, old_name, new_name) { - if(dt==me.doctype) - me.rename_notify(dt, old_name, new_name); - }); } setup_meta() { @@ -107,7 +96,7 @@ frappe.ui.form.Form = class FrappeForm { this.script_manager.setup(); this.watch_model_updates(); - if(!this.meta.hide_toolbar) { + if(!this.meta.hide_toolbar && frappe.boot.desk_settings.timeline) { this.footer = new frappe.ui.form.Footer({ frm: this, parent: $('
').appendTo(this.page.main.parent()) @@ -117,6 +106,7 @@ frappe.ui.form.Form = class FrappeForm { this.setup_file_drop(); this.setup_doctype_actions(); this.setup_docinfo_change_listener(); + this.setup_notify_on_rename(); this.setup_done = true; } @@ -175,6 +165,7 @@ frappe.ui.form.Form = class FrappeForm { this.dashboard = new frappe.ui.form.Dashboard({ frm: this, + parent: $('
').insertAfter(this.layout.wrapper.find('.form-message')) }); // workflow state @@ -222,6 +213,13 @@ frappe.ui.form.Form = class FrappeForm { }); } + setup_notify_on_rename() { + $(document).on('rename', (ev, dt, old_name, new_name) => { + if(dt==this.doctype) + this.rename_notify(dt, old_name, new_name); + }); + } + setup_file_drop() { var me = this; this.$wrapper.on('dragenter dragover', false) @@ -445,11 +443,13 @@ frappe.ui.form.Form = class FrappeForm { this.layout.doc = this.doc; this.layout.attach_doc_and_docfields(); - this.sidebar = new frappe.ui.form.Sidebar({ - frm: this, - page: this.page - }); - this.sidebar.make(); + if (frappe.boot.desk_settings.form_sidebar) { + this.sidebar = new frappe.ui.form.Sidebar({ + frm: this, + page: this.page + }); + this.sidebar.make(); + } // clear layout message this.layout.show_message(); @@ -1665,7 +1665,7 @@ frappe.ui.form.Form = class FrappeForm { }); driver.defineSteps(steps); - frappe.route.on('change', () => driver.reset()); + frappe.router.on('change', () => driver.reset()); driver.start(); } diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 075d1cec84..27b3b869c5 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -121,7 +121,7 @@ frappe.form.formatters = { {onclick: docfield.link_onclick.replace(/"/g, '"'), value:value}); } else if(docfield && doctype) { return ` ${__(options && options.label || value)}` diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index ce149e4371..1d0f1f8ffd 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -127,14 +127,6 @@ frappe.ui.form.Layout = Class.extend({ if (this.no_opening_section()) { this.fields.unshift({fieldtype: 'Section Break'}); } - - this.fields.unshift({ - fieldtype: 'Section Break', - fieldname: '_form_dashboard', - cssClass: 'form-dashboard', - collapsible: 1, - // hidden: 1 - }); }, replace_field: function(fieldname, df, render) { @@ -312,10 +304,6 @@ frappe.ui.form.Layout = Class.extend({ collapse = false; } - if (df.fieldname === '_form_dashboard') { - collapse = localStorage.getItem('collapseFormDashboard')==='yes' ? true : false; - } - section.collapse(collapse); } } @@ -587,17 +575,13 @@ frappe.ui.form.Section = Class.extend({ wrapper: this.wrapper }; - if (this.df.collapsible && this.df.fieldname !== '_form_dashboard') { - this.collapse(true); - } - this.refresh(); }, make: function() { if (!this.layout.page) { this.layout.page = $('
').appendTo(this.layout.wrapper); } - let make_card = this.layout.card_layout && this.df.fieldname !== '_form_dashboard'; + let make_card = this.layout.card_layout; this.wrapper = $(`
`) .appendTo(this.layout.page); this.layout.sections.push(this); @@ -664,18 +648,12 @@ frappe.ui.form.Section = Class.extend({ hide = !this.body.hasClass("hide"); } - if (this.df.fieldname==='_form_dashboard') { - localStorage.setItem('collapseFormDashboard', hide ? 'yes' : 'no'); - } - this.body.toggleClass("hide", hide); this.head.toggleClass("collapsed", hide); let indicator_icon = hide ? 'down' : 'up-line'; this.indicator & this.indicator.html(frappe.utils.icon(indicator_icon, 'sm', 'mb-1')); - // this.indicator && this.indicator.toggleClass("octicon-chevron-down", hide); - // this.indicator && this.indicator.toggleClass("octicon-chevron-up", !hide); // refresh signature fields this.fields_list.forEach((f) => { diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 28cc9bb28b..bdbf0b687d 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -29,7 +29,9 @@ frappe.ui.form.Toolbar = Class.extend({ } }, set_title: function() { - if(this.frm.meta.title_field) { + if (this.frm.is_new()) { + var title = __('New {0}', [this.frm.meta.name]); + } else if (this.frm.meta.title_field) { let title_field = (this.frm.doc[this.frm.meta.title_field] || "").toString().trim(); var title = strip_html(title_field || this.frm.docname); if(this.frm.doc.__islocal || title === this.frm.docname || this.frm.meta.autoname==="hash") { @@ -198,30 +200,40 @@ frappe.ui.form.Toolbar = Class.extend({ make_menu: function() { this.page.clear_icons(); this.page.clear_menu(); - var me = this; - var p = this.frm.perm[0]; - var docstatus = cint(this.frm.doc.docstatus); - var is_submittable = frappe.model.is_submittable(this.frm.doc.doctype) - var issingle = this.frm.meta.issingle; - var print_settings = frappe.model.get_doc(":Print Settings", "Print Settings") - var allow_print_for_draft = cint(print_settings.allow_print_for_draft); - var allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled); - // Navigate - if (!this.frm.is_new() && !issingle) { - this.page.add_action_icon("left", function() { - me.frm.navigate_records(1); - }, 'prev-doc', __("Previous Document")); - this.page.add_action_icon("right", function() { - me.frm.navigate_records(0); - }, 'next-doc', __("Next Document")); + if (frappe.boot.desk_settings.form_sidebar) { + this.make_navigation(); + this.make_menu_items(); } + }, + make_navigation() { + // Navigate + if (!this.frm.is_new() && !this.frm.meta.issingle) { + this.page.add_action_icon("left", () => { + this.frm.navigate_records(1); + }, 'prev-doc'); + this.page.add_action_icon("right", ()=> { + this.frm.navigate_records(0); + }, 'next-doc'); + } + }, + + make_menu_items() { // Print + const me = this; + const p = this.frm.perm[0]; + const docstatus = cint(this.frm.doc.docstatus); + const is_submittable = frappe.model.is_submittable(this.frm.doc.doctype) + + const print_settings = frappe.model.get_doc(":Print Settings", "Print Settings") + const allow_print_for_draft = cint(print_settings.allow_print_for_draft); + const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled); + if (!is_submittable || docstatus == 1 || (allow_print_for_cancelled && docstatus == 2)|| (allow_print_for_draft && docstatus == 0)) { - if (frappe.model.can_print(null, me.frm) && !issingle) { + if (frappe.model.can_print(null, me.frm) && !this.frm.meta.issingle) { this.page.add_menu_item(__("Print"), function() { me.frm.print_doc(); }, true); @@ -283,30 +295,7 @@ frappe.ui.form.Toolbar = Class.extend({ }); } - if (frappe.user_roles.includes("System Manager")) { - let is_doctype_form = me.frm.doctype === 'DocType'; - let doctype = is_doctype_form ? me.frm.docname : me.frm.doctype; - let is_doctype_custom = is_doctype_form ? me.frm.doc.custom : false; - - if (doctype != 'DocType' && !is_doctype_custom && me.frm.meta.issingle === 0) { - this.page.add_menu_item(__("Customize"), function() { - if (me.frm.meta && me.frm.meta.custom) { - frappe.set_route('Form', 'DocType', doctype); - } else { - frappe.set_route('Form', 'Customize Form', { - doc_type: doctype - }); - } - }, true); - } - - if (frappe.boot.developer_mode===1 && !is_doctype_form) { - // edit doctype - this.page.add_menu_item(__("Edit DocType"), function() { - frappe.set_route('Form', 'DocType', me.frm.doctype); - }, true); - } - } + this.make_customize_buttons(); // Auto Repeat if(this.can_repeat()) { @@ -325,6 +314,35 @@ frappe.ui.form.Toolbar = Class.extend({ }); } }, + + make_customize_buttons() { + if (frappe.user_roles.includes("System Manager")) { + let is_doctype_form = this.frm.doctype === 'DocType'; + let doctype = is_doctype_form ? this.frm.docname : this.frm.doctype; + let is_doctype_custom = is_doctype_form ? this.frm.doc.custom : false; + + if (doctype != 'DocType' && !is_doctype_custom && this.frm.meta.issingle === 0) { + this.page.add_menu_item(__("Customize"), () => { + if (this.frm.meta && this.frm.meta.custom) { + frappe.set_route('Form', 'DocType', doctype); + } else { + frappe.set_route('Form', 'Customize Form', { + doc_type: doctype + }); + } + }, true); + } + + if (frappe.boot.developer_mode===1 && !is_doctype_form) { + // edit doctype + this.page.add_menu_item(__("Edit DocType"), () => { + frappe.set_route('Form', 'DocType', this.frm.doctype); + }, true); + } + } + + }, + can_repeat: function() { return this.frm.meta.allow_auto_repeat && !this.frm.is_new() diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index c67de6afc9..96b06aac95 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -182,15 +182,17 @@ frappe.views.BaseList = class BaseList { 'Dashboard': 'dashboard' } - this.views_menu = this.page.add_custom_button_group(__(`{0} View`, [this.view_name]), icon_map[this.view_name] || 'list'); - this.views_list = new frappe.views.Views({ - doctype: this.doctype, - parent: this.views_menu, - page: this.page, - list_view: this, - sidebar: this.list_sidebar, - icon_map: icon_map - }); + if (frappe.boot.desk_settings.view_switcher) { + this.views_menu = this.page.add_custom_button_group(__(`{0} View`, [this.view_name]), icon_map[this.view_name] || 'list'); + this.views_list = new frappe.views.Views({ + doctype: this.doctype, + parent: this.views_menu, + page: this.page, + list_view: this, + sidebar: this.list_sidebar, + icon_map: icon_map + }); + } } set_default_secondary_action() { @@ -236,7 +238,7 @@ frappe.views.BaseList = class BaseList { } setup_side_bar() { - if (this.hide_sidebar) return; + if (this.hide_sidebar || !frappe.boot.desk_settings.list_sidebar) return; this.list_sidebar = new frappe.views.ListSidebar({ doctype: this.doctype, stats: this.stats, diff --git a/frappe/public/js/frappe/list/list_filter.js b/frappe/public/js/frappe/list/list_filter.js index fbbf5e583f..c02755d50c 100644 --- a/frappe/public/js/frappe/list/list_filter.js +++ b/frappe/public/js/frappe/list/list_filter.js @@ -167,6 +167,7 @@ export default class ListFilter { } get_list_filters() { + if (frappe.session.user === 'Guest') return Promise.resolve(); return frappe.db .get_list('List Filter', { fields: ['name', 'filter_name', 'for_user', 'filters'], diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 4ba2f76b67..8a005ffdfb 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -9,7 +9,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { const doctype = route[1]; if (route.length === 2) { - // List/{doctype} => List/{doctype}/{last_view} or List const user_settings = frappe.get_user_settings(doctype); const last_view = user_settings.last_view; frappe.set_route( @@ -871,7 +870,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { ? encodeURIComponent(doc.name) : doc.name; - return "/app/form/" + frappe.router.slug(frappe.router.doctype_layout || this.doctype) + "/" + docname; + return `/app/${frappe.router.slug(frappe.router.doctype_layout || this.doctype)}/${docname}`; } get_seen_class(doc) { @@ -905,6 +904,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { let escaped_subject = frappe.utils.escape_html(subject); const seen = this.get_seen_class(doc); + console.log(this.get_form_link(doc)); let subject_html = ` ${item.name}`; + html += `
  • ${item.name}
  • `; }); } @@ -181,7 +181,7 @@ frappe.views.Views = class Views { reports.map((r) => { if (!r.ref_doctype || r.ref_doctype == this.doctype) { const report_type = r.report_type === 'Report Builder' ? - `list/${r.ref_doctype}/report` : 'query-report'; + `/app/list/${r.ref_doctype}/report` : 'query-report'; const route = r.route || report_type + '/' + (r.title || r.name); @@ -233,11 +233,11 @@ frappe.views.Views = class Views { // has standard calendar view calendars.push({ name: 'Default', - route: `list/${this.doctype}/calendar/default` + route: `/app/${this.get_doctype_route()}/view/calendar/default` }); } result.map(calendar => { - calendars.push({name: calendar.name, route: `list/${doctype}/calendar/${calendar.name}`}); + calendars.push({name: calendar.name, route: `/app/${this.get_doctype_route()}/view/calendar/${calendar.name}`}); }); return calendars; @@ -249,7 +249,7 @@ frappe.views.Views = class Views { let accounts = frappe.boot.email_accounts; accounts.forEach(account => { let email_account = (account.email_id == "All Accounts") ? "All Accounts" : account.email_account; - let route = ["List", "Communication", "Inbox", email_account].join('/'); + let route = `/app/communication/inbox/${email_account}`; let display_name = ["All Accounts", "Sent Mail", "Spam", "Trash"].includes(account.email_id) ? __(account.email_id) : account.email_account; @@ -262,4 +262,8 @@ frappe.views.Views = class Views { return accounts_to_add; } + + get_doctype_route() { + return frappe.router.slug(frappe.router.doctype_layout || this.doctype); + } } \ No newline at end of file diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 09cb55d8fc..210a4ae5d3 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -126,6 +126,7 @@ $.extend(frappe.model, { var user_permissions = frappe.defaults.get_user_permissions(); let allowed_records = []; let default_doc = null; + let value = null; if(user_permissions) { ({allowed_records, default_doc} = frappe.perm.filter_allowed_docs_for_doctype(user_permissions[df.options], doc.doctype)); } @@ -139,71 +140,79 @@ $.extend(frappe.model, { if (df.fieldtype==="Link" && df.options!=="User") { // If user permission has Is Default enabled or single-user permission has found against respective doctype. if (has_user_permissions && default_doc) { - return default_doc; - } - - if(!df.ignore_user_permissions) { + value = default_doc; + } else { // 2 - look in user defaults - var user_defaults = frappe.defaults.get_user_defaults(df.options); - if (user_defaults && user_defaults.length===1) { - // Use User Permission value when only when it has a single value - user_default = user_defaults[0]; + + if(!df.ignore_user_permissions) { + var user_defaults = frappe.defaults.get_user_defaults(df.options); + if (user_defaults && user_defaults.length===1) { + // Use User Permission value when only when it has a single value + user_default = user_defaults[0]; + } + } + + else if (!user_default) { + user_default = frappe.defaults.get_user_default(df.fieldname); + } + + else if(!user_default && df.remember_last_selected_value && frappe.boot.user.last_selected_values) { + user_default = frappe.boot.user.last_selected_values[df.options]; + } + + var is_allowed_user_default = user_default && + (!has_user_permissions || allowed_records.includes(user_default)); + + // is this user default also allowed as per user permissions? + if (is_allowed_user_default) { + value = user_default; } } - if (!user_default) { - user_default = frappe.defaults.get_user_default(df.fieldname); - } - - if(!user_default && df.remember_last_selected_value && frappe.boot.user.last_selected_values) { - user_default = frappe.boot.user.last_selected_values[df.options]; - } - - var is_allowed_user_default = user_default && - (!has_user_permissions || allowed_records.includes(user_default)); - - // is this user default also allowed as per user permissions? - if (is_allowed_user_default) { - return user_default; - } } // 3 - look in default of docfield - if (df['default']) { + if (!value || df['default']) { const default_val = String(df['default']); if (default_val == "__user" || default_val.toLowerCase() == "user") { - return frappe.session.user; + value = frappe.session.user; } else if (default_val == "user_fullname") { - return frappe.session.user_fullname; + value = frappe.session.user_fullname; } else if (default_val == "Today") { - return frappe.datetime.get_today(); + value = frappe.datetime.get_today(); } else if ((default_val || "").toLowerCase() === "now") { - return frappe.datetime.now_datetime(); + value = frappe.datetime.now_datetime(); } else if (default_val[0]===":") { var boot_doc = frappe.model.get_default_from_boot_docs(df, doc, parent_doc); var is_allowed_boot_doc = !has_user_permissions || allowed_records.includes(boot_doc); if (is_allowed_boot_doc) { - return boot_doc; + value = boot_doc; } } else if (df.fieldname===meta.title_field) { // ignore defaults for title field - return ""; + value = ""; + } else { + // is this default value is also allowed as per user permissions? + var is_allowed_default = !has_user_permissions || allowed_records.includes(df.default); + if (df.fieldtype!=="Link" || df.options==="User" || is_allowed_default) { + value = df["default"]; + } } - // is this default value is also allowed as per user permissions? - var is_allowed_default = !has_user_permissions || allowed_records.includes(df.default); - if (df.fieldtype!=="Link" || df.options==="User" || is_allowed_default) { - return df["default"]; - } } else if (df.fieldtype=="Time") { - return frappe.datetime.now_time(); + value = frappe.datetime.now_time(); } + + // set it here so we know it was set as a default + df.__default_value = value; + + return value; }, get_default_from_boot_docs: function(df, doc, parent_doc) { diff --git a/frappe/public/js/frappe/model/user_settings.js b/frappe/public/js/frappe/model/user_settings.js index 30bd98008d..4d11429ce4 100644 --- a/frappe/public/js/frappe/model/user_settings.js +++ b/frappe/public/js/frappe/model/user_settings.js @@ -6,6 +6,8 @@ $.extend(frappe.model.user_settings, { .then(r => JSON.parse(r.message || '{}')); }, save: function(doctype, key, value) { + if (frappe.session.user === 'Guest') return Promise.resolve(); + const old_user_settings = frappe.model.user_settings[doctype] || {}; const new_user_settings = $.extend(true, {}, old_user_settings); // deep copy @@ -31,6 +33,7 @@ $.extend(frappe.model.user_settings, { return this.update(doctype, user_settings); }, update: function(doctype, user_settings) { + if (frappe.session.user === 'Guest') return Promise.resolve(); return frappe.call({ method: 'frappe.model.utils.user_settings.save', args: { diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index a49b916f6e..b3a28999f4 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -8,6 +8,7 @@ frappe.provide('frappe.request.error_handlers'); frappe.request.url = '/'; frappe.request.ajax_count = 0; frappe.request.waiting_for_ajax = []; +frappe.request.logs = {} frappe.xcall = function(method, params) { return new Promise((resolve, reject) => { @@ -89,6 +90,11 @@ frappe.call = function(opts) { delete args.cmd; } + // debouce if required + if (opts.debounce && frappe.request.is_fresh(args, opts.debounce)) { + return Promise.resolve(); + } + return frappe.request.call({ type: opts.type || "POST", args: args, @@ -127,7 +133,7 @@ frappe.request.call = function(opts) { message: __('The resource you are looking for is not available')}); }, 403: function(xhr) { - if (frappe.session.user === 'Guest') { + if (frappe.session.logged_in_user !== 'Guest') { // session expired frappe.app.handle_session_expired(); } @@ -239,7 +245,7 @@ frappe.request.call = function(opts) { status_code_handler(data, xhr); } } catch(e) { - console.log("Unable to handle success response"); // eslint-disable-line + console.log("Unable to handle success response", data); // eslint-disable-line console.trace(e); // eslint-disable-line } @@ -278,6 +284,26 @@ frappe.request.call = function(opts) { }); } +frappe.request.is_fresh = function(args, threshold) { + // return true if a request with similar args has been sent recently + if (!frappe.request.logs[args.cmd]) { + frappe.request.logs[args.cmd] = []; + } + + for (let past_request of frappe.request.logs[args.cmd]) { + // check if request has same args and was made recently + if ((new Date() - past_request.timestamp) < threshold + && frappe.utils.deep_equal(args, past_request.args)) { + console.log('throttled'); + return true; + } + } + + // log the request + frappe.request.logs[args.cmd].push({args: args, timestamp: new Date()}); + return false; +} + // call execute serverside request frappe.request.prepare = function(opts) { $("body").attr("data-ajax-state", "triggered"); @@ -322,7 +348,8 @@ frappe.request.cleanup = function(opts, r) { if(r) { // session expired? - Guest has no business here! - if (r.session_expired || frappe.session.user === "Guest") { + if (r.session_expired || + (frappe.session.user === 'Guest' && frappe.session.logged_in_user !== "Guest")) { frappe.app.handle_session_expired(); return; } diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index d3fe28dd10..5421a60676 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -20,12 +20,13 @@ $(window).on('hashchange', function() { let sub_path = frappe.router.get_sub_path(window.location.hash); window.location.hash = ''; frappe.router.push_state(sub_path); + return false; } }); window.addEventListener('popstate', () => { // forward-back button, just re-render based on current route - frappe.route(); + frappe.router.route(); }); // routing v2, capture all clicks so that the target is managed with push-state @@ -52,18 +53,40 @@ $('body').on('click', 'a', function(e) { } // target has "/app, this is a v2 style route. - if (e.currentTarget.pathname && - (e.currentTarget.pathname.startsWith('/app') || e.currentTarget.pathname.startsWith('app'))) { + if (e.currentTarget.pathname && frappe.router.is_app_route()) { return override(e, e.currentTarget.pathname); } }); frappe.router = { current_route: null, - doctype_names: {}, - factory_views: ['form', 'list', 'report', 'tree', 'print'], + routes: {}, + factory_views: ['form', 'list', 'report', 'tree', 'print', 'dashboard'], + list_views: ['list', 'kanban', 'report', 'calendar', 'tree', 'gantt', 'dashboard', 'image', 'inbox'], layout_mapped: {}, + is_app_route() { + // desk paths must begin with /app or doctype route + let path = window.location.pathname; + if (path.substr(0, 1) === '/') path = path.substr(1); + path = path.split('/'); + if (path[0]) { + return path[0]==='app'; + } + }, + + setup() { + // setup the route names by forming slugs of the given doctypes + for(let doctype of frappe.boot.user.can_read) { + this.routes[this.slug(doctype)] = {doctype: doctype}; + } + if (frappe.boot.doctype_layouts) { + for (let doctype_layout of frappe.boot.doctype_layouts) { + this.routes[this.slug(doctype_layout.name)] = {doctype: doctype_layout.document_type, doctype_layout: doctype_layout.name }; + } + } + }, + route() { // resolve the route from the URL or hash // translate it so the objects are well defined @@ -71,64 +94,81 @@ frappe.router = { if (!frappe.app) return; - let sub_path = frappe.router.get_sub_path(); - if (frappe.router.re_route(sub_path)) return; + let sub_path = this.get_sub_path(); + if (this.re_route(sub_path)) return; - frappe.router.translate_doctype_name().then(() => { - frappe.router.set_history(sub_path); - - if (frappe.router.current_route[0]) { - frappe.router.render_page(); - } else { - // Show home - frappe.views.pageview.show(''); - } - - frappe.router.set_title(); - frappe.route.trigger('change'); - }); + this.current_route = this.parse(); + this.set_history(sub_path); + this.render(); + this.set_title(); + this.trigger('change'); }, - translate_doctype_name() { - return new Promise((resolve) => { - const route = frappe.router.current_route = frappe.router.parse(); - const factory = route[0].toLowerCase(); - const set_name = () => { - const d = frappe.router.doctype_names[route[1]]; - route[1] = d.doctype; - frappe.router.doctype_layout = d.doctype_layout; - resolve(); - }; + parse(route) { + route = this.get_sub_path_string(route).split('/'); + route = $.map(route, this.decode_component); + this.set_route_options_from_url(route); + return this.convert_to_standard_route(route); + }, - if (frappe.router.factory_views.includes(factory)) { - // translate the doctype to its original name - if (frappe.router.doctype_names[route[1]]) { - set_name(); + convert_to_standard_route(route) { + // /app/user = ["List", "User"] + // /app/user/view/report = ["List", "User", "Report"] + // /app/user/view/tree = ["Tree", "User"] + // /app/user/user-001 = ["Form", "User", "user-001"] + // /app/user/user-001 = ["Form", "User", "user-001"] + let standard_route = route; + let doctype_route = this.routes[route[0]]; + + if (doctype_route) { + // doctype route + if (route[1]) { + if (route[2] && route[1]==='view') { + if (route[2].toLowerCase()==='tree') { + standard_route = ['Tree', doctype_route.doctype]; + } else { + standard_route = ['List', doctype_route.doctype, frappe.utils.to_title_case(route[2])]; + } } else { - frappe.xcall('frappe.desk.utils.get_doctype_name', {name: route[1]}).then((data) => { - frappe.router.doctype_names[route[1]] = data.name_map; - set_name(); - }); + standard_route = ['Form', doctype_route.doctype, route[1]]; } + } else if (frappe.model.is_single(doctype_route.doctype)) { + standard_route = ['Form', doctype_route.doctype, doctype_route.doctype]; } else { - resolve(); + standard_route = ['List', doctype_route.doctype, 'List']; } - }); + + if (doctype_route.doctype_layout) { + // set the layout + this.doctype_layout = doctype_route.doctype_layout; + } + } + + return standard_route; }, set_history(sub_path) { - frappe.route_history.push(frappe.router.current_route); + frappe.route_history.push(this.current_route); frappe.route_titles[sub_path] = frappe._original_title || document.title; frappe.ui.hide_open_dialog(); }, + render() { + if (this.current_route[0]) { + this.render_page(); + } else { + // Show home + frappe.views.pageview.show(''); + } + }, + render_page() { // create the page generator (factory) object and call `show` // if there is no generator, render the `Page` object // first the router needs to know if its a "page", "doctype", "workspace" - const route = frappe.router.current_route; + const route = this.current_route; const factory = frappe.utils.to_title_case(route[0]); if (factory === 'Workspace') { frappe.views.pageview.show(''); @@ -155,12 +195,12 @@ frappe.router = { re_route(sub_path) { if (frappe.re_route[sub_path] !== undefined) { // after saving a doc, for example, - // "New DocType 1" and the renamed "TestDocType", both exist in history + // "new-doctype-1" and the renamed "TestDocType", both exist in history // now if we try to go back, - // it doesn't allow us to go back to the one prior to "New DocType 1" + // it doesn't allow us to go back to the one prior to "new-doctype-1" // Hence if this check is true, instead of changing location hash, - // we just do a back to go to the doc previous to the "New DocType 1" - var re_route_val = frappe.router.get_sub_path(frappe.re_route[sub_path]); + // we just do a back to go to the doc previous to the "new-doctype-1" + var re_route_val = this.get_sub_path(frappe.re_route[sub_path]); if (decodeURIComponent(re_route_val) === decodeURIComponent(sub_path)) { window.history.back(); return true; @@ -185,32 +225,14 @@ frappe.router = { // set the route (push state) with given arguments // example 1: frappe.set_route('a', 'b', 'c'); // example 2: frappe.set_route(['a', 'b', 'c']); - // example 3: frappe.set_route('a/b/c'); + // example 3: frappe.set_route('a/b/c'); + let route = arguments; return new Promise(resolve => { - var route = arguments; - if (route.length===1 && $.isArray(route[0])) { - // called as frappe.set_route(['a', 'b', 'c']); - route = route[0]; - } - - if (route.length===1 && route[0].includes('/')) { - // called as frappe.set_route('a/b/c') - route = $.map(route[0].split('/'), frappe.router.decode_component); - } - - if (route && route[0] == '') { - route.shift(); - } - - if (route && ['desk', 'app'].includes(route[0])) { - // we only need subpath, remove "app" (or "desk") - route.shift(); - } - - frappe.router.slug_parts(route); - const sub_path = frappe.router.make_url_from_list(route); - frappe.router.push_state(sub_path); + route = this.get_route_from_arguments(route); + route = this.convert_from_standard_route(route); + const sub_path = this.make_url(route); + this.push_state(sub_path); setTimeout(() => { frappe.after_ajax && frappe.after_ajax(() => { @@ -220,19 +242,67 @@ frappe.router = { }); }, - slug_parts(route) { - // slug doctype + get_route_from_arguments(route) { + if (route.length===1 && $.isArray(route[0])) { + // called as frappe.set_route(['a', 'b', 'c']); + route = route[0]; + } - // if app is part of the route, then first 2 elements are "" and "app" - if (route[0] && frappe.router.factory_views.includes(route[0].toLowerCase())) { - route[0] = route[0].toLowerCase(); - route[1] = frappe.router.slug(route[1]); + if (route.length===1 && route[0].includes('/')) { + // called as frappe.set_route('a/b/c') + route = $.map(route[0].split('/'), this.decode_component); + } + + if (route && route[0] == '') { + route.shift(); + } + + if (route && ['desk', 'app'].includes(route[0])) { + // we only need subpath, remove "app" (or "desk") + route.shift(); + } + + return route; + + }, + + convert_from_standard_route(route) { + // ["List", "Sales Order"] => /sales-order + // ["Form", "Sales Order", "SO-0001"] => /sales-order/SO-0001 + // ["Tree", "Account"] = /account/view/tree + + const view = route[0].toLowerCase(); + if (view === 'list') { + if (route[2] && route[2] !== 'list') { + const new_route = [this.slug(route[1]), 'view', route[2].toLowerCase()]; + + // calendar / inbox + if (route[3]) new_route.push(route[3]); + return new_route; + } else { + return [this.slug(route[1])] + } + } else if (view === 'form') { + return [this.slug(route[1]), route[2]] + } else if (view === 'tree') { + return [this.slug(route[1]), 'view', 'tree']; } return route; }, - make_url_from_list(params) { - return $.map(params, function(a) { + slug_parts(route) { + // slug doctype + + // if app is part of the route, then first 2 elements are "" and "app" + if (route[0] && this.factory_views.includes(route[0].toLowerCase())) { + route[0] = route[0].toLowerCase(); + route[1] = this.slug(route[1]); + } + return route; + }, + + make_url(params) { + return '/app/' + $.map(params, function(a) { if ($.isPlainObject(a)) { frappe.route_options = a; return null; @@ -247,10 +317,8 @@ frappe.router = { }).join('/'); }, - push_state(sub_path) { + push_state(url) { // change the URL and call the router - const url = `/app/${sub_path}`; - if (window.location.pathname !== url) { // cleanup any remenants of v1 routing window.location.hash = ''; @@ -259,19 +327,10 @@ frappe.router = { history.pushState(null, null, url); // now process the route - frappe.router.route(); + this.route(); } }, - parse(route) { - route = frappe.router.get_sub_path_string(route).split('/'); - route = $.map(route, frappe.router.decode_component); - - frappe.router.set_route_options_from_url(route); - - return route; - }, - get_sub_path_string(route) { // return clean sub_path from hash or url // supports both v1 and v2 routing @@ -279,7 +338,7 @@ frappe.router = { route = window.location.hash || window.location.pathname; } - return frappe.router.strip_prefix(route); + return this.strip_prefix(route); }, strip_prefix(route) { @@ -292,8 +351,8 @@ frappe.router = { }, get_sub_path(route) { - var sub_path = frappe.router.get_sub_path_string(route); - route = $.map(sub_path.split('/'), frappe.router.decode_component).join('/'); + var sub_path = this.get_sub_path_string(route); + route = $.map(sub_path.split('/'), this.decode_component).join('/'); return route; }, @@ -333,10 +392,9 @@ frappe.router = { }; // global functions for backward compatibility -frappe.route = frappe.router.route; frappe.get_route = () => frappe.router.current_route; frappe.get_route_str = () => frappe.router.current_route.join('/'); -frappe.set_route = frappe.router.set_route; +frappe.set_route = function() { return frappe.router.set_route.apply(frappe.router, arguments) }; frappe.get_prev_route = function() { if (frappe.route_history && frappe.route_history.length > 1) { @@ -356,4 +414,4 @@ frappe.has_route_options = function() { return Boolean(Object.keys(frappe.route_options || {}).length); }; -frappe.utils.make_event_emitter(frappe.route); +frappe.utils.make_event_emitter(frappe.router); diff --git a/frappe/public/js/frappe/router_history.js b/frappe/public/js/frappe/router_history.js index 61fc4d6b13..c64c3fc9f2 100644 --- a/frappe/public/js/frappe/router_history.js +++ b/frappe/public/js/frappe/router_history.js @@ -1,19 +1,21 @@ -frappe.provide('frappe.route'); frappe.route_history_queue = []; const routes_to_skip = ['Form', 'social', 'setup-wizard', 'recorder']; const save_routes = frappe.utils.debounce(() => { + if (frappe.session.user === 'Guest') return; const routes = frappe.route_history_queue; frappe.route_history_queue = []; + frappe.xcall('frappe.deferred_insert.deferred_insert', { 'doctype': 'Route History', 'records': routes }).catch(() => { frappe.route_history_queue.concat(routes); - }); + }); + }, 10000); -frappe.route.on('change', () => { +frappe.router.on('change', () => { const route = frappe.get_route(); if (is_route_useful(route)) { frappe.route_history_queue.push({ diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index afdb40118a..998ddbedc9 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -12,8 +12,7 @@ frappe.ui.Notifications = class Notifications { } make() { - this.dropdown = $('.navbar').find('.dropdown-notifications'); - this.dropdown.removeClass("hidden") + this.dropdown = $('.navbar').find('.dropdown-notifications').removeClass('hidden'); this.dropdown_list = this.dropdown.find('.notifications-list'); this.header_items = this.dropdown_list.find('.header-items'); this.header_actions = this.dropdown_list.find('.header-actions'); diff --git a/frappe/public/js/frappe/ui/tags.js b/frappe/public/js/frappe/ui/tags.js index c7c0d6c4bb..e609dc6bc2 100644 --- a/frappe/public/js/frappe/ui/tags.js +++ b/frappe/public/js/frappe/ui/tags.js @@ -20,7 +20,7 @@ frappe.ui.Tags = class { setup(parent, placeholder) { this.$ul = parent; - this.$input = $(``); + this.$input = $(``); this.$inputWrapper = this.get_list_element(this.$input); this.$placeholder = this.get_list_element($(`${placeholder}`)); diff --git a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js index 577b3d3f14..f9ec4c789d 100644 --- a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js +++ b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js @@ -7,6 +7,7 @@ frappe.search.AwesomeBar = Class.extend({ setup: function(element) { var me = this; + $('.search-bar').removeClass('hidden'); var $input = $(element); var input = $input.get(0); @@ -122,6 +123,7 @@ frappe.search.AwesomeBar = Class.extend({ $input.blur(); }); frappe.search.utils.setup_recent(); + frappe.tags.utils.fetch_tags(); }, add_help: function() { diff --git a/frappe/public/js/frappe/ui/toolbar/navbar.html b/frappe/public/js/frappe/ui/toolbar/navbar.html index 5ddb5ffc83..2349b2205e 100644 --- a/frappe/public/js/frappe/ui/toolbar/navbar.html +++ b/frappe/public/js/frappe/ui/toolbar/navbar.html @@ -6,7 +6,7 @@