Bläddra i källkod

fix(routing): even cleaner routing

version-14
Rushabh Mehta 4 år sedan
förälder
incheckning
87cdf5334c
24 ändrade filer med 302 tillägg och 194 borttagningar
  1. +11
    -0
      frappe/boot.py
  2. +5
    -4
      frappe/core/doctype/doctype/doctype.json
  3. +23
    -19
      frappe/core/doctype/doctype/doctype.py
  4. +7
    -0
      frappe/core/doctype/doctype/patches/set_route.py
  5. +8
    -1
      frappe/custom/doctype/doctype_layout/doctype_layout.json
  6. +4
    -0
      frappe/custom/doctype/doctype_layout/doctype_layout.py
  7. +13
    -0
      frappe/custom/doctype/doctype_layout/patches/convert_web_forms_to_doctype_layout.py
  8. +6
    -3
      frappe/desk/utils.py
  9. +1
    -1
      frappe/public/js/frappe/desk.js
  10. +1
    -1
      frappe/public/js/frappe/form/dashboard.js
  11. +2
    -1
      frappe/public/js/frappe/form/form.js
  12. +1
    -1
      frappe/public/js/frappe/form/formatters.js
  13. +56
    -43
      frappe/public/js/frappe/form/toolbar.js
  14. +11
    -9
      frappe/public/js/frappe/list/base_list.js
  15. +2
    -1
      frappe/public/js/frappe/list/list_view.js
  16. +130
    -90
      frappe/public/js/frappe/router.js
  17. +1
    -2
      frappe/public/js/frappe/router_history.js
  18. +2
    -2
      frappe/public/js/frappe/ui/toolbar/navbar.html
  19. +1
    -1
      frappe/public/js/frappe/ui/toolbar/search.js
  20. +9
    -9
      frappe/public/js/frappe/utils/utils.js
  21. +4
    -3
      frappe/public/js/frappe/views/breadcrumbs.js
  22. +1
    -1
      frappe/public/js/frappe/views/formview.js
  23. +2
    -2
      frappe/website/doctype/blog_post/blog_post.json
  24. +1
    -0
      frappe/website/doctype/blog_post/blog_post.py

+ 11
- 0
frappe/boot.py Visa fil

@@ -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_routes(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,16 @@ def get_additional_filters_from_hooks():

return filter_config

def add_routes(bootinfo):
# add routes for readable doctypes
doctype_routes = frappe.get_all('DocType', ['name', 'route'], dict(istable = 0))
bootinfo.routes = {d.route:dict(doctype=d.name) for d in doctype_routes if d.route}

# add routes for layouts
doctype_layout_routes = frappe.get_all('DocType Layout', ['name', 'route', 'document_type'],
dict(document_type = ('in', bootinfo.user.can_read)))
bootinfo.routes.update({d.route:dict(doctype=d.document_type, doctype_layout=d.name) for d in doctype_layout_routes if d.route})

def get_desk_settings():
role_list = frappe.get_all('Role', fields=['*'], filters=dict(
name=['in', frappe.get_roles()]


+ 5
- 4
frappe/core/doctype/doctype/doctype.json Visa fil

@@ -16,6 +16,7 @@
"is_tree",
"editable_grid",
"quick_entry",
"route",
"cb01",
"track_changes",
"track_seen",
@@ -71,7 +72,6 @@
"has_web_view",
"allow_guest_to_view",
"index_web_pages_for_search",
"route",
"is_published_field",
"advanced",
"engine"
@@ -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-07 11:48:57.395126",
"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",


+ 23
- 19
frappe/core/doctype/doctype/doctype.py Visa fil

@@ -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]:


+ 7
- 0
frappe/core/doctype/doctype/patches/set_route.py Visa fil

@@ -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)

+ 8
- 1
frappe/custom/doctype/doctype_layout/doctype_layout.json Visa fil

@@ -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-04 12:29:12.838656",
"modified_by": "Administrator",
"module": "Custom",
"name": "DocType Layout",


+ 4
- 0
frappe/custom/doctype/doctype_layout/doctype_layout.py Visa fil

@@ -7,6 +7,10 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document

from frappe.desk.utils import get_route

class DocTypeLayout(Document):
def validate(self):
if not self.route:
self.route = get_route(self.name)
frappe.cache().delete_value('doctype_name_map')

+ 13
- 0
frappe/custom/doctype/doctype_layout/patches/convert_web_forms_to_doctype_layout.py Visa fil

@@ -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)

+ 6
- 3
frappe/desk/utils.py Visa fil

@@ -12,10 +12,10 @@ def get_doctype_name(name):
def get_name_map():
name_map = {}
for d in frappe.get_all('DocType'):
name_map[d.name.lower().replace(' ', '-')] = frappe._dict(doctype = d.name)
name_map[get_doctype_route(d.name)] = 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)
name_map[get_doctype_route(d.name)] = frappe._dict(doctype = d.document_type, doctype_layout = d.name)

return name_map

@@ -25,4 +25,7 @@ def get_doctype_name(name):
# return the layout object
frappe.response.docs.append(frappe.get_doc('DocType Layout', data.name_map.get('doctype_layout')).as_dict())

return data
return data

def get_doctype_route(name):
return name.lower().replace(' ', '-')

+ 1
- 1
frappe/public/js/frappe/desk.js Visa fil

@@ -168,7 +168,7 @@ frappe.Application = Class.extend({
localStorage.removeItem("session_last_route");
} else {
// route to home page
frappe.route();
frappe.router.route();
}
},



+ 1
- 1
frappe/public/js/frappe/form/dashboard.js Visa fil

@@ -161,7 +161,7 @@ frappe.ui.form.Dashboard = class FormDashboard {

refresh() {
this.reset();
if (this.frm.doc.__islocal) {
if (this.frm.doc.__islocal || !frappe.boot.desk_settings.form_dashboard) {
return;
}



+ 2
- 1
frappe/public/js/frappe/form/form.js Visa fil

@@ -1,4 +1,5 @@
frappe.provide('frappe.ui.form');
frappe.provide('frappe.model.docinfo');

import './quick_entry';
import './toolbar';
@@ -1668,7 +1669,7 @@ frappe.ui.form.Form = class FrappeForm {
});

driver.defineSteps(steps);
frappe.route.on('change', () => driver.reset());
frappe.router.on('change', () => driver.reset());
driver.start();
}



+ 1
- 1
frappe/public/js/frappe/form/formatters.js Visa fil

@@ -121,7 +121,7 @@ frappe.form.formatters = {
{onclick: docfield.link_onclick.replace(/"/g, '"'), value:value});
} else if(docfield && doctype) {
return `<a
href="/app/form/${encodeURIComponent(frappe.router.slug(doctype))}/${encodeURIComponent(original_value)}"
href="/app/${encodeURIComponent(frappe.router.slug(doctype))}/${encodeURIComponent(original_value)}"
data-doctype="${doctype}"
data-name="${original_value}">
${__(options && options.label || value)}</a>`


+ 56
- 43
frappe/public/js/frappe/form/toolbar.js Visa fil

@@ -199,32 +199,39 @@ frappe.ui.form.Toolbar = Class.extend({
this.page.clear_icons();
this.page.clear_menu();

if (frappe.session.user === 'Guest') return;

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);
if (frappe.boot.desk_settings.form_sidebar) {
this.make_navigation();
this.make_menu_items();
}
},

make_navigation() {
// 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 (!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);
@@ -286,30 +293,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()) {
@@ -328,6 +312,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()


+ 11
- 9
frappe/public/js/frappe/list/base_list.js Visa fil

@@ -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() {


+ 2
- 1
frappe/public/js/frappe/list/list_view.js Visa fil

@@ -871,7 +871,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 +905,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 = `
<input class="level-item list-row-checkbox hidden-xs" type="checkbox"


+ 130
- 90
frappe/public/js/frappe/router.js Visa fil

@@ -26,7 +26,7 @@ $(window).on('hashchange', function() {

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
@@ -53,18 +53,27 @@ $('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'],
factory_views: ['form', 'list', 'report', 'tree', 'print', 'dashboard'],
list_views: ['list', 'kanban', 'report', 'calendar', 'tree', 'gantt', 'dashboard'],
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';
}
},

route() {
// resolve the route from the URL or hash
// translate it so the objects are well defined
@@ -72,64 +81,81 @@ frappe.router = {

if (!frappe.app) return;

let sub_path = frappe.router.get_sub_path();
if (frappe.router.re_route(sub_path)) return;

frappe.router.translate_doctype_name().then(() => {
frappe.router.set_history(sub_path);
let sub_path = this.get_sub_path();
if (this.re_route(sub_path)) return;

if (frappe.router.current_route[0]) {
frappe.router.render_page();
} else {
// Show home
frappe.views.pageview.show('');
}
this.current_route = this.parse();
this.set_history(sub_path);
this.render();
this.set_title();
this.trigger('change');
},

frappe.router.set_title();
frappe.route.trigger('change');
});
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);
},

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();
};

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/views/report = ["List", "User", "Report"]
// /app/user/views/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 = frappe.boot.routes[route[0]];

if (doctype_route) {
// doctype route
if (route[1]) {
if (route[2] && route[1]==='views') {
if (route[2].toLowerCase()==='tree') {
standard_route = ['Tree', doctype_route.doctype];
} else {
standard_route = ['List', doctype_route.doctype, 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('');
@@ -161,7 +187,7 @@ frappe.router = {
// 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]);
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;
@@ -186,32 +212,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(() => {
@@ -221,19 +229,63 @@ frappe.router = {
});
},

get_route_from_arguments(route) {
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('/'), 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') {
return [this.slug(route[1]), 'view', view]
} 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;
},

slug_parts(route) {
// slug doctype

// 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())) {
if (route[0] && this.factory_views.includes(route[0].toLowerCase())) {
route[0] = route[0].toLowerCase();
route[1] = frappe.router.slug(route[1]);
route[1] = this.slug(route[1]);
}
return route;
},

make_url_from_list(params) {
return $.map(params, function(a) {
make_url(params) {
return '/app/' + $.map(params, function(a) {
if ($.isPlainObject(a)) {
frappe.route_options = a;
return null;
@@ -248,10 +300,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 = '';
@@ -260,19 +310,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
@@ -280,7 +321,7 @@ frappe.router = {
route = window.location.hash || window.location.pathname;
}

return frappe.router.strip_prefix(route);
return this.strip_prefix(route);
},

strip_prefix(route) {
@@ -293,8 +334,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;
},
@@ -334,10 +375,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() { frappe.router.set_route.apply(frappe.router, arguments) };

frappe.get_prev_route = function() {
if (frappe.route_history && frappe.route_history.length > 1) {
@@ -357,4 +397,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);

+ 1
- 2
frappe/public/js/frappe/router_history.js Visa fil

@@ -1,4 +1,3 @@
frappe.provide('frappe.route');
frappe.route_history_queue = [];
const routes_to_skip = ['Form', 'social', 'setup-wizard', 'recorder'];

@@ -16,7 +15,7 @@ const save_routes = frappe.utils.debounce(() => {

}, 10000);

frappe.route.on('change', () => {
frappe.router.on('change', () => {
const route = frappe.get_route();
if (is_route_useful(route)) {
frappe.route_history_queue.push({


+ 2
- 2
frappe/public/js/frappe/ui/toolbar/navbar.html Visa fil

@@ -59,8 +59,8 @@
</span>
</a>
</li>
<li class="vertical-bar hidden d-none d-sm-block"></li>
<li class="nav-item dropdown dropdown-help dropdown-mobile hidden d-none d-lg-block">
<li class="vertical-bar d-none d-sm-block"></li>
<li class="nav-item dropdown dropdown-help dropdown-mobile d-none d-lg-block">
<a class="nav-link" data-toggle="dropdown" href="#" onclick="return false;">
{{ __("Help") }}
<span>


+ 1
- 1
frappe/public/js/frappe/ui/toolbar/search.js Visa fil

@@ -402,7 +402,7 @@ frappe.search.SearchDialog = class {
frappe.set_route(result.route);
// hashchange didn't fire!
if (window.location.hash == previous_hash) {
frappe.route();
frappe.router.route();
}
}
});


+ 9
- 9
frappe/public/js/frappe/utils/utils.js Visa fil

@@ -816,7 +816,7 @@ Object.assign(frappe.utils, {
display_text = display_text || name;
doctype = encodeURIComponent(doctype);
name = encodeURIComponent(name);
const route = ['/app/Form', doctype, name].join('/');
const route = `/app/${frappe.router.slug(doctype)}/${name}`;
if (html) {
return `<a href="${route}">${display_text}</a>`;
}
@@ -1127,7 +1127,7 @@ Object.assign(frappe.utils, {
let doctype_slug = frappe.router.slug(item.doctype);
if (frappe.model.is_single(item.doctype)) {
route = "form/" + doctype_slug;
route = doctype_slug;
} else {
if (!item.doc_view) {
if (frappe.model.is_tree(item.doctype)) {
@@ -1142,22 +1142,22 @@ Object.assign(frappe.utils, {
if (item.filters) {
frappe.route_options = item.filters;
}
route = "list/" + doctype_slug;
route = doctype_slug;
break;
case "Tree":
route = "tree/" + doctype_slug;
route = `${doctype_slug}/view/tree`;
break;
case "Report Builder":
route = "list/" + doctype_slug + "/Report";
route = `${doctype_slug}/view/report`;
break;
case "Dashboard":
route = "list/" + doctype_slug + "/Dashboard";
route = `${doctype_slug}/view/dashboard`;
break;
case "New":
route = "form/" + doctype_slug + "/New " + item.doctype;
route = `${doctype_slug}/new`;
break;
case "Calendar":
route = "list/" + doctype_slug + "/Calendar/Default";
route = `${doctype_slug}/view/calendar/Default`;
break;
default:
frappe.throw({ message: __("Not a valid DocType view:") + item.doc_view, title: __("Unknown View") });
@@ -1167,7 +1167,7 @@ Object.assign(frappe.utils, {
} else if (type === "report" && item.is_query_report) {
route = "query-report/" + item.name;
} else if (type === "report") {
route = "List/" + frappe.router.slug(item.doctype) + "/Report/" + item.name;
route = frappe.router.slug(item.doctype) + "/view/report/" + item.name;
} else if (type === "page") {
route = item.name;
} else if (type === "dashboard") {


+ 4
- 3
frappe/public/js/frappe/views/breadcrumbs.js Visa fil

@@ -114,11 +114,12 @@ frappe.breadcrumbs = {
// no user listview for non-system managers and single doctypes
} else {
let route;
const doctype_route = frappe.router.slug(frappe.router.doctype_layout || breadcrumbs.doctype);
if (frappe.boot.treeviews.indexOf(breadcrumbs.doctype) !== -1) {
let view = frappe.model.user_settings[breadcrumbs.doctype].last_view || 'Tree';
route = view + '/' + breadcrumbs.doctype;
route = `${doctype_route}/view/${view}`;
} else {
route = 'list/' + (frappe.router.doctype_layout || breadcrumbs.doctype);
route = doctype_route;
}
$(`<li><a href="/app/${route}">${doctype}</a></li>`)
.appendTo($breadcrumbs)
@@ -132,7 +133,7 @@ frappe.breadcrumbs = {
if (breadcrumbs.doctype && frappe.get_route()[0] === "print") {
set_list_breadcrumb(breadcrumbs.doctype);
let docname = frappe.get_route()[2];
let form_route = `/app/form/${frappe.router.slug(breadcrumbs.doctype)}/${docname}`;
let form_route = `/app/${frappe.router.slug(breadcrumbs.doctype)}/${docname}`;
$(`<li><a href="${form_route}">${docname}</a></li>`)
.appendTo($breadcrumbs);
}


+ 1
- 1
frappe/public/js/frappe/views/formview.js Visa fil

@@ -54,7 +54,7 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory {
}

const doc = frappe.get_doc(doctype, name);
if (doc && (doc.__islocal || frappe.model.is_fresh(doc))) {
if (doc && frappe.model.get_docinfo(doctype, name) && (doc.__islocal || frappe.model.is_fresh(doc))) {
// is document available and recent?
this.render(doctype_layout, name);
} else {


+ 2
- 2
frappe/website/doctype/blog_post/blog_post.json Visa fil

@@ -200,7 +200,7 @@
"is_published_field": "published",
"links": [],
"max_attachments": 5,
"modified": "2020-08-31 21:01:51.100349",
"modified": "2020-12-07 23:27:23.644512",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Post",
@@ -229,7 +229,7 @@
"write": 1
}
],
"route": "/blog",
"route": "blog-post",
"sort_field": "modified",
"sort_order": "ASC",
"title_field": "title",


+ 1
- 0
frappe/website/doctype/blog_post/blog_post.py Visa fil

@@ -14,6 +14,7 @@ from frappe.website.utils import (find_first_image, get_html_content_based_on_ty

class BlogPost(WebsiteGenerator):
website = frappe._dict(
route = 'blog',
order_by = "published_on desc"
)



Laddar…
Avbryt
Spara