Merge pull request #11917 from rmehta/unified-desk

feat(routing): New routing style, not using hashes, also /desk -> /app
This commit is contained in:
Rushabh Mehta 2020-11-11 17:48:55 +05:30 committed by GitHub
commit 742860f56e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
91 changed files with 447 additions and 260 deletions

View file

@ -2,7 +2,7 @@ context('API Resources', () => {
before(() => {
cy.visit('/login');
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
it('Creates two Comments', () => {

View file

@ -2,7 +2,7 @@ context('Awesome Bar', () => {
before(() => {
cy.visit('/login');
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
beforeEach(() => {
@ -16,7 +16,7 @@ context('Awesome Bar', () => {
cy.get('h1').should('contain', 'To Do');
cy.location('hash').should('eq', '#List/ToDo/List');
cy.location('hash').should('eq', '/app/List/ToDo/List');
});
it('find text in doctype list', () => {

View file

@ -1,7 +1,7 @@
context('Control Barcode', () => {
beforeEach(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
function get_dialog_with_barcode() {

View file

@ -1,7 +1,7 @@
context('Control Duration', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
function get_dialog_with_duration(hide_days=0, hide_seconds=0) {

View file

@ -1,11 +1,11 @@
context('Control Link', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
beforeEach(() => {
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
cy.create_records({
doctype: 'ToDo',
description: 'this is a test todo for link'
@ -77,7 +77,7 @@ context('Control Link', () => {
cy.get('.frappe-control[data-fieldname=link] .link-btn')
.should('be.visible')
.click();
cy.location('hash').should('eq', `#Form/ToDo/${todos[0]}`);
cy.location('hash').should('eq', `/app/Form/ToDo/${todos[0]}`);
});
});
});

View file

@ -1,7 +1,7 @@
context('Control Rating', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
function get_dialog_with_rating() {

View file

@ -4,7 +4,7 @@ const doctype_name = datetime_doctype.name;
context('Control Date, Time and DateTime', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
return cy.insert_doc('DocType', datetime_doctype, true);
});

View file

@ -1,7 +1,7 @@
context('Depends On', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
return cy.window().its('frappe').then(frappe => {
return frappe.call('frappe.tests.ui_test_helpers.create_doctype', {
name: 'Test Depends On',

View file

@ -1,7 +1,7 @@
context('FileUploader', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
function open_upload_dialog() {

View file

@ -1,13 +1,13 @@
context('Form', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
});
});
it('create a new form', () => {
cy.visit('/desk#Form/ToDo/New ToDo 1');
cy.visit('/app/Form/ToDo/New ToDo 1');
cy.fill_field('description', 'this is a test todo', 'Text Editor').blur();
cy.wait(300);
cy.get('.page-title').should('contain', 'Not Saved');
@ -18,20 +18,20 @@ context('Form', () => {
}).as('form_save');
cy.get('.primary-action').click();
cy.wait('@form_save').its('status').should('eq', 200);
cy.visit('/desk#List/ToDo');
cy.location('hash').should('eq', '#List/ToDo/List');
cy.visit('/app/List/ToDo');
cy.location('hash').should('eq', '/app/List/ToDo/List');
cy.get('h1').should('be.visible').and('contain', 'To Do');
cy.get('.list-row').should('contain', 'this is a test todo');
});
it('navigates between documents with child table list filters applied', () => {
cy.visit('/desk#List/Contact');
cy.location('hash').should('eq', '#List/Contact/List');
cy.visit('/app/List/Contact');
cy.location('hash').should('eq', '/app/List/Contact/List');
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
cy.get('.fieldname-select-area').should('exist');
cy.get('.fieldname-select-area input').type('Number{enter}', { force: true });
cy.get('.filter-field .input-with-feedback.form-control').type('123', { force: true });
cy.get('.filter-box .btn:contains("Apply")').click({ force: true });
cy.visit('/desk#Form/Contact/Test Form Contact 3');
cy.visit('/app/Form/Contact/Test Form Contact 3');
cy.get('.prev-doc').should('be.visible').click();
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
cy.get('.btn-modal-close:visible').click();
@ -50,7 +50,7 @@ context('Form', () => {
let website_input = 'website.in';
let expectBackgroundColor = 'rgb(255, 220, 220)';
cy.visit('/desk#Form/Contact/New Contact 1');
cy.visit('/app/Form/Contact/New Contact 1');
cy.get('.frappe-control[data-fieldname="email_ids"]').as('table');
cy.get('@table').find('button.grid-add-row').click();
cy.get('.grid-body .rows [data-fieldname="email_id"]').click();

View file

@ -1,24 +1,24 @@
context('Grid Pagination', () => {
beforeEach(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_contact_phone_nos_records");
});
});
it('creates pages for child table', () => {
cy.visit('/desk#Form/Contact/Test Contact');
cy.visit('/app/Form/Contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').find('.current-page-number').should('contain', '1');
cy.get('@table').find('.total-page-number').should('contain', '20');
cy.get('@table').find('.grid-body .grid-row').should('have.length', 50);
});
it('goes to the next and previous page', () => {
cy.visit('/desk#Form/Contact/Test Contact');
cy.visit('/app/Form/Contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').find('.next-page').click();
cy.get('@table').find('.current-page-number').should('contain', '2');
@ -28,7 +28,7 @@ context('Grid Pagination', () => {
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '1');
});
it('adds and deletes rows and changes page', ()=> {
cy.visit('/desk#Form/Contact/Test Contact');
cy.visit('/app/Form/Contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').find('button.grid-add-row').click();
cy.get('@table').find('.grid-body .row-index').should('contain', 1001);
@ -41,7 +41,7 @@ context('Grid Pagination', () => {
cy.get('@table').find('.total-page-number').should('contain', '20');
});
// it('deletes all rows', ()=> {
// cy.visit('/desk#Form/Contact/Test Contact');
// cy.visit('/app/Form/Contact/Test Contact');
// cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
// cy.get('@table').find('.grid-heading-row .grid-row-check').click({force: true});
// cy.get('@table').find('button.grid-remove-all-rows').click();

View file

@ -1,7 +1,7 @@
context('List View', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
return cy.window().its('frappe').then(frappe => {
return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
});

View file

@ -1,16 +1,16 @@
context('List View Settings', () => {
beforeEach(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
it('Default settings', () => {
cy.visit('/desk#List/DocType/List');
cy.visit('/app/List/DocType/List');
cy.get('.list-count').should('contain', "20 of");
cy.get('.sidebar-stat').should('contain', "Tags");
});
it('disable count and sidebar stats then verify', () => {
cy.wait(300);
cy.visit('/desk#List/DocType/List');
cy.visit('/app/List/DocType/List');
cy.wait(300);
cy.get('.list-count').should('contain', "20 of");
cy.get('button').contains('Menu').click();

View file

@ -35,7 +35,7 @@ context('Login', () => {
cy.get('#login_password').type(Cypress.config('adminPassword'));
cy.get('.btn-login').click();
cy.location('pathname').should('eq', '/desk');
cy.location('pathname').should('eq', '/app');
cy.window().its('frappe.session.user').should('eq', 'Administrator');
});

View file

@ -1,11 +1,11 @@
context('Query Report', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
});
it('add custom column in report', () => {
cy.visit('/desk#query-report/Permitted Documents For User');
cy.visit('/app/query-report/Permitted Documents For User');
cy.get('div[class="page-form flex"]', {timeout: 60000}).should('have.length', 1).then(()=>{
cy.get('#page-query-report input[data-fieldname="user"]').as('input');

View file

@ -4,14 +4,14 @@ context('Recorder', () => {
});
it('Navigate to Recorder', () => {
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
cy.awesomebar('recorder');
cy.get('h1').should('contain', 'Recorder');
cy.location('hash').should('eq', '#recorder');
});
it('Recorder Empty State', () => {
cy.visit('/desk#recorder');
cy.visit('/app/recorder');
cy.get('.title-text').should('contain', 'Recorder');
cy.get('.indicator').should('contain', 'Inactive').should('have.class', 'red');
@ -24,21 +24,21 @@ context('Recorder', () => {
});
it('Recorder Start', () => {
cy.visit('/desk#recorder');
cy.visit('/app/recorder');
cy.get('.primary-action').should('contain', 'Start').click();
cy.get('.indicator').should('contain', 'Active').should('have.class', 'green');
cy.get('.msg-box').should('contain', 'No Requests');
cy.server();
cy.visit('/desk#List/DocType/List');
cy.visit('/app/List/DocType/List');
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.wait('@list_refresh');
cy.get('.title-text').should('contain', 'DocType');
cy.get('.list-count').should('contain', '20 of ');
cy.visit('/desk#recorder');
cy.visit('/app/recorder');
cy.get('.title-text').should('contain', 'Recorder');
cy.get('.result-list').should('contain', '/api/method/frappe.desk.reportview.get');
@ -48,11 +48,11 @@ context('Recorder', () => {
});
it('Recorder View Request', () => {
cy.visit('/desk#recorder');
cy.visit('/app/recorder');
cy.get('.primary-action').should('contain', 'Start').click();
cy.server();
cy.visit('/desk#List/DocType/List');
cy.visit('/app/List/DocType/List');
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.wait('@list_refresh');
@ -62,7 +62,7 @@ context('Recorder', () => {
// temporarily commenting out theses tests as they seem to be
// randomly failing maybe due a backround event
// cy.visit('/desk#recorder');
// cy.visit('/app/recorder');
// cy.get('.list-row-container span').contains('/api/method/frappe').click();

View file

@ -4,13 +4,13 @@ context('Relative Timeframe', () => {
});
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
cy.window().its('frappe').then(frappe => {
frappe.call("frappe.tests.ui_test_helpers.create_todo_records");
});
});
it('sets relative timespan filter for last week and filters list', () => {
cy.visit('/desk#List/ToDo/List');
cy.visit('/app/List/ToDo/List');
cy.get('.list-row:contains("this is fourth todo")').should('exist');
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
cy.get('.fieldname-select-area').should('exist');
@ -29,7 +29,7 @@ context('Relative Timeframe', () => {
cy.wait('@save_user_settings');
});
it('sets relative timespan filter for next week and filters list', () => {
cy.visit('/desk#List/ToDo/List');
cy.visit('/app/List/ToDo/List');
cy.get('.list-row:contains("this is fourth todo")').should('exist');
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });

View file

@ -4,7 +4,7 @@ const doctype_name = custom_submittable_doctype.name;
context('Report View', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/workspace/Website');
cy.insert_doc('DocType', custom_submittable_doctype, true);
cy.clear_cache();
cy.insert_doc(doctype_name, {
@ -18,7 +18,7 @@ context('Report View', () => {
it('Field with enabled allow_on_submit should be editable.', () => {
cy.server();
cy.route('POST', 'api/method/frappe.client.set_value').as('value-update');
cy.visit(`/desk#List/${doctype_name}/Report`);
cy.visit(`/app/List/${doctype_name}/Report`);
// check status column added from docstatus
cy.get('.dt-row-0 > .dt-cell--col-3').should('contain', 'Submitted');
let cell = cy.get('.dt-row-0 > .dt-cell--col-4');

View file

@ -210,13 +210,13 @@ Cypress.Commands.add('awesomebar', text => {
Cypress.Commands.add('new_form', doctype => {
let route = `Form/${doctype}/New ${doctype} 1`;
cy.visit(`/desk#${route}`);
cy.visit(`/app/${route}`);
cy.get('body').should('have.attr', 'data-route', route);
cy.get('body').should('have.attr', 'data-ajax-state', 'complete');
});
Cypress.Commands.add('go_to_list', doctype => {
cy.visit(`/desk#List/${doctype}/List`);
cy.visit(`/app/List/${doctype}/List`);
});
Cypress.Commands.add('clear_cache', () => {

View file

@ -1611,7 +1611,7 @@ def log_error(message=None, title=_("Error")):
method=title)).insert(ignore_permissions=True)
def get_desk_link(doctype, name):
html = '<a href="#Form/{doctype}/{name}" style="font-weight: bold;">{doctype_local} {name}</a>'
html = '<a href="/app/Form/{doctype}/{name}" style="font-weight: bold;">{doctype_local} {name}</a>'
return html.format(
doctype=doctype,
name=name,

View file

@ -173,7 +173,7 @@ class LoginManager:
frappe.local.cookie_manager.set_cookie("system_user", "yes")
if not resume:
frappe.local.response['message'] = 'Logged In'
frappe.local.response["home_page"] = "/desk"
frappe.local.response["home_page"] = "/app"
if not resume:
frappe.response["full_name"] = self.full_name

View file

@ -30,7 +30,7 @@ frappe.ui.form.on('Auto Repeat', {
refresh: function(frm) {
// auto repeat message
if (frm.is_new()) {
let customize_form_link = `<a href="#Form/Customize Form">${__('Customize Form')}</a>`;
let customize_form_link = `<a href="/app/Form/Customize Form">${__('Customize Form')}</a>`;
frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link]));
}

View file

@ -88,6 +88,7 @@ def get_bootinfo():
bootinfo.frequently_visited_links = frequently_visited_links()
bootinfo.link_preview_doctypes = get_link_preview_doctypes()
bootinfo.additional_filters_config = get_additional_filters_from_hooks()
bootinfo.desk_settings = get_desk_settings()
return bootinfo
@ -225,7 +226,7 @@ def load_translations(bootinfo):
def get_user_info():
user_info = frappe.db.get_all('User', fields=['`name`', 'full_name as fullname', 'user_image as image',
'gender', 'email', 'username', 'bio', 'location', 'interest', 'banner_image', 'allowed_in_mentions'],
filters=dict(enabled=1, user_type=['!=', 'Website User']))
filters=dict(enabled=1))
user_info_map = {d.name: d for d in user_info}
@ -308,3 +309,16 @@ def get_additional_filters_from_hooks():
return filter_config
def get_desk_settings():
role_list = frappe.get_all('Role', fields=['*'], filters=dict(
name=['in', frappe.get_roles()]
))
desk_settings = {}
from frappe.core.doctype.role.role import desk_properties
for role in role_list:
for key in desk_properties:
desk_settings[key] = desk_settings.get(key) or role.get(key)
return desk_settings

View file

@ -11,7 +11,7 @@ frappe.listview_settings["Deleted Document"] = {
if (r.message) {
function body(docnames) {
const html = docnames.map(docname => {
return `<li><a href='/desk#Form/Deleted Document/${docname}'>${docname}</a></li>`;
return `<li><a href='/app/Form/Deleted Document/${docname}'>${docname}</a></li>`;
});
return "<br><ul>" + html.join("");
}

View file

@ -24,11 +24,12 @@ frappe.ui.form.on('DocType', {
if (!frm.is_new() && !frm.doc.istable) {
if (frm.doc.issingle) {
frm.add_custom_button(__('Go to {0}', [frm.doc.name]), () => {
frappe.set_route('Form', frm.doc.name);
window.open(`/app/Form/${frm.doc.name}`);
// frappe.set_route('Form', frm.doc.name);
});
} else {
frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
frappe.set_route('List', frm.doc.name, 'List');
window.open(`/app/List/${frm.doc.name}/List`);
});
}
}

View file

@ -36,7 +36,7 @@ def has_unseen_error_log(user):
def _get_response(show_alert=True):
return {
'show_alert': True,
'message': _("You have unseen {0}").format('<a href="/desk#List/Error%20Log/List"> Error Logs </a>')
'message': _("You have unseen {0}").format('<a href="/app/List/Error%20Log/List"> Error Logs </a>')
}
if frappe.db.sql_list("select name from `tabError Log` where seen = 0 limit 1"):

View file

@ -0,0 +1,9 @@
import frappe
from ..role import desk_properties
def execute():
for role in frappe.get_all('Role', ['name', 'desk_access']):
role_doc = frappe.get_doc('Role', role.name)
for key in desk_properties:
role_doc.set(key, role_doc.desk_access)
role_doc.save()

View file

@ -13,7 +13,19 @@
"column_break_4",
"disabled",
"desk_access",
"two_factor_auth"
"two_factor_auth",
"navigation_settings_section",
"search_bar",
"notification",
"chat",
"list_settings_section",
"list_sidebar",
"bulk_actions",
"view_switcher",
"form_settings_section",
"form_sidebar",
"timeline",
"dashboard"
],
"fields": [
{
@ -60,12 +72,82 @@
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "navigation_settings_section",
"fieldtype": "Section Break",
"label": "Navigation Settings"
},
{
"default": "1",
"fieldname": "search_bar",
"fieldtype": "Check",
"label": "Search Bar"
},
{
"default": "1",
"fieldname": "notification",
"fieldtype": "Check",
"label": "Notification"
},
{
"default": "1",
"fieldname": "chat",
"fieldtype": "Check",
"label": "Chat"
},
{
"fieldname": "list_settings_section",
"fieldtype": "Section Break",
"label": "List Settings"
},
{
"default": "1",
"fieldname": "list_sidebar",
"fieldtype": "Check",
"label": "Sidebar"
},
{
"default": "1",
"fieldname": "bulk_actions",
"fieldtype": "Check",
"label": "Bulk Actions"
},
{
"fieldname": "form_settings_section",
"fieldtype": "Section Break",
"label": "Form Settings"
},
{
"default": "1",
"fieldname": "form_sidebar",
"fieldtype": "Check",
"label": "Sidebar"
},
{
"default": "1",
"fieldname": "timeline",
"fieldtype": "Check",
"label": "Timeline"
},
{
"default": "1",
"fieldname": "dashboard",
"fieldtype": "Check",
"label": "Dashboard"
},
{
"default": "1",
"fieldname": "view_switcher",
"fieldtype": "Check",
"label": "View Switcher"
}
],
"icon": "fa fa-bookmark",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-08-06 15:42:59.036960",
"modified": "2020-11-11 17:29:13.149522",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",

View file

@ -6,6 +6,9 @@ import frappe
from frappe.model.document import Document
desk_properties = ("search_bar", "notification", "chat", "list_sidebar",
"bulk_actions", "view_switcher", "form_sidebar", "timeline", "dashboard")
class Role(Document):
def before_rename(self, old, new, merge=False):
if old in ("Guest", "Administrator", "System Manager", "All"):
@ -16,11 +19,25 @@ class Role(Document):
def validate(self):
if self.disabled:
if self.name in ("Guest", "Administrator", "System Manager", "All"):
frappe.throw(frappe._("Standard roles cannot be disabled"))
else:
frappe.db.sql("delete from `tabHas Role` where role = %s", self.name)
frappe.clear_cache()
self.disable_role()
else:
self.set_desk_properties()
def disable_role(self):
if self.name in ("Guest", "Administrator", "System Manager", "All"):
frappe.throw(frappe._("Standard roles cannot be disabled"))
else:
self.remove_roles()
def set_desk_properties(self):
# set if desk_access is not allowed, unset all desk properties
if not self.desk_access:
for key in desk_properties:
self.set(key, 0)
def remove_roles(self):
frappe.db.sql("delete from `tabHas Role` where role = %s", self.name)
frappe.clear_cache()
def on_update(self):
'''update system user desk access if this has changed in this update'''

View file

@ -182,20 +182,17 @@ class User(Document):
def share_with_self(self):
if self.user_type=="System User":
frappe.share.add(self.doctype, self.name, self.name, write=1, share=1,
flags={"ignore_share_permission": True})
else:
frappe.share.remove(self.doctype, self.name, self.name,
flags={"ignore_share_permission": True, "ignore_permissions": True})
frappe.share.add(self.doctype, self.name, self.name, write=1, share=1,
flags={"ignore_share_permission": True})
def validate_share(self, docshare):
if docshare.user == self.name:
if self.user_type=="System User":
if docshare.share != 1:
frappe.throw(_("Sorry! User should have complete access to their own record."))
else:
frappe.throw(_("Sorry! Sharing with Website User is prohibited."))
pass
# if docshare.user == self.name:
# if self.user_type=="System User":
# if docshare.share != 1:
# frappe.throw(_("Sorry! User should have complete access to their own record."))
# else:
# frappe.throw(_("Sorry! Sharing with Website User is prohibited."))
def send_password_notification(self, new_password):
try:
@ -580,7 +577,7 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password=
frappe.db.set_value("User", user, "reset_password_key", "")
if user_doc.user_type == "System User":
return "/desk"
return "/app"
else:
return redirect_url if redirect_url else "/"

View file

@ -311,7 +311,7 @@ frappe.PermissionEngine = class PermissionEngine {
},
callback: function (r) {
r.message = $.map(r.message, function (p) {
return $.format('<a href="#Form/User/{0}">{1}</a>', [p, p]);
return $.format('<a href="/app/Form/User/{0}">{1}</a>', [p, p]);
});
frappe.msgprint(__("Users with role {0}:", [__(role)])
+ "<br>" + r.message.join("<br>"));

View file

@ -5,8 +5,8 @@
<li>{%= __("Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Set User Permissions.") %}</li>
<li>{%= __("Permissions get applied on Users based on what Roles they are assigned.") %}</li>
<li>{%= __("Roles can be set for users from their User page.") %}
<a href="#List/User">{%= __("Setup > User") %}</a></li>
<li>{%= __("The system provides many pre-defined roles. You can add new roles to set finer permissions.") %}<a href="#List/Role"> {%= __("Add a New Role") %}</a></li>
<a href="/app/List/User">{%= __("Setup > User") %}</a></li>
<li>{%= __("The system provides many pre-defined roles. You can add new roles to set finer permissions.") %}<a href="/app/List/Role"> {%= __("Add a New Role") %}</a></li>
<li>{%= __("Permissions are automatically applied to Standard Reports and searches.") %}</li>
<li>{%= __("As a best practice, do not assign the same set of permission rule to different Roles. Instead, set multiple Roles to the same User.") %}</li>
</ol>
@ -24,13 +24,13 @@
<li>{%= __("Permissions at level 0 are Document Level permissions, i.e. they are primary for access to the document.") %}</li>
<li>{%= __("If a Role does not have access at Level 0, then higher levels are meaningless.") %}</li>
<li>{%= __("Permissions at higher levels are Field Level permissions. All Fields have a Permission Level set against them and the rules defined at that permissions apply to the field. This is useful in case you want to hide or make certain field read-only for certain Roles.") %}</li>
<li>{%= __("You can use Customize Form to set levels on fields.") %} <a href="#Form/Customize Form">{%= __("Setup > Customize Form") %}</a></li>
<li>{%= __("You can use Customize Form to set levels on fields.") %} <a href="/app/Form/Customize Form">{%= __("Setup > Customize Form") %}</a></li>
</ol>
<hr>
<h4>{%= __("User Permissions") %}:</h4>
<ol>
<li>{%= __("User Permissions are used to limit users to specific records.") %}
<a href="#List/User Permission">{%= __("Setup > User Permissions") %}</a></li>
<a href="/app/List/User Permission">{%= __("Setup > User Permissions") %}</a></li>
<li>{%= __("Select Document Types to set which User Permissions are used to limit access.") %}</li>
<li>{%= __("Once you have set this, the users will only be able access documents (eg. Blog Post) where the link exists (eg. Blogger).") %}</li>
<li>{%= __("Apart from System Manager, roles with Set User Permissions right can set permissions for other users for that Document Type.") %}</li>

View file

@ -234,7 +234,7 @@ class TestCustomizeForm(unittest.TestCase):
testdt1.delete()
def test_custom_action(self):
test_route = '#List/DocType'
test_route = '/app/List/DocType'
# create a dummy action (route)
d = self.get_customize_form("Event")

View file

@ -80,7 +80,7 @@ def get_non_standard_warning_message(non_standard_docs_map):
def get_html(docs, doctype):
html = '<p>{}</p>'.format(frappe.bold(doctype))
for doc in docs:
html += '<div><a href="#Form/{doctype}/{doc}">{doc}</a></div>'.format(doctype=doctype, doc=doc)
html += '<div><a href="/app/Form/{doctype}/{doc}">{doc}</a></div>'.format(doctype=doctype, doc=doc)
html += '<br>'
return html

View file

@ -12,7 +12,7 @@ from frappe.cache_manager import build_domain_restriced_doctype_cache, build_dom
@frappe.whitelist()
def get(module):
"""Returns data (sections, list of reports, counts) to render module view in desk:
`/desk/#Module/[name]`."""
`/app/#Module/[name]`."""
data = get_data(module)
out = {
@ -90,7 +90,7 @@ def get_data(module, build=True):
return data
def build_config_from_file(module):
"""Build module info from `app/config/desktop.py` files."""
"""Build module info from `app/config/apptop.py` files."""
data = []
module = frappe.scrub(module)
@ -150,7 +150,7 @@ def add_section(data, label, icon, items):
def add_custom_doctypes(data, doctype_info):
"""Adds Custom DocTypes to modules setup via `config/desktop.py`."""
"""Adds Custom DocTypes to modules setup via `config/apptop.py`."""
add_section(data, _("Documents"), "fa fa-star",
[d for d in doctype_info if (d.custom and d.document_type in ("Document", "Transaction"))])
@ -258,7 +258,7 @@ def config_exists(app, module):
return False
def add_setup_section(config, app, module, label, icon):
"""Add common sections to `/desk#Module/Setup`"""
"""Add common sections to `/app/Module/Setup`"""
try:
setup_section = get_setup_section(app, module, label, icon)
if setup_section:

View file

@ -348,7 +348,7 @@ class Leaderboard {
return fieldname === this.options.selected_filter_item;
}));
const link = `#Form/${this.options.selected_doctype}/${item.name}`;
const link = `/app/Form/${this.options.selected_doctype}/${item.name}`;
const name_html = item.formatted_name ?
`<span class="text-muted ellipsis list-id">${item.formatted_name}</span>`
: `<a class="grey list-id ellipsis" href="${link}"> ${item.name} </a>`;

View file

@ -212,7 +212,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
}
setTimeout(function () {
// Reload
window.location.href = '/desk';
window.location.href = '/app';
}, 2000);
}

View file

@ -24,7 +24,7 @@ def execute(filters=None):
for todo in todo_list:
if todo.owner==frappe.session.user or todo.assigned_by==frappe.session.user:
if todo.reference_type:
todo.reference = """<a href="#Form/%s/%s">%s: %s</a>""" % (todo.reference_type,
todo.reference = """<a href="/app/Form/%s/%s">%s: %s</a>""" % (todo.reference_type,
todo.reference_name, todo.reference_type, todo.reference_name)
else:
todo.reference = None

View file

@ -203,7 +203,7 @@ frappe.ui.form.on('Notification', {
frappe.notification.setup_example_message(frm);
if (frm.doc.channel === 'SMS' && frm.doc.__islocal) {
frm.set_df_property('channel',
'description', `To use SMS Channel, initialize <a href="#Form/SMS Settings">SMS Settings</a>.`);
'description', `To use SMS Channel, initialize <a href="/app/Form/SMS Settings">SMS Settings</a>.`);
} else {
frm.set_df_property('channel', 'description', ` `);
}

View file

@ -29,16 +29,18 @@ page_js = {
# website
app_include_js = [
"assets/js/libs.min.js",
"assets/js/desk.min.js",
"assets/js/list.min.js",
"assets/js/form.min.js",
"assets/js/control.min.js",
"assets/js/report.min.js",
"/assets/js/libs.min.js",
"/assets/js/desk.min.js",
"/assets/js/list.min.js",
"/assets/js/form.min.js",
"/assets/js/control.min.js",
"/assets/js/report.min.js",
]
app_include_css = [
"assets/css/desk.min.css",
"assets/css/report.min.css",
"/assets/css/desk.min.css",
"/assets/css/list.min.css",
"/assets/css/form.min.css",
"/assets/css/report.min.css",
]
doctype_js = {

View file

@ -4,7 +4,7 @@
frappe.ui.form.on("Google Calendar", {
refresh: function(frm) {
if (frm.is_new()) {
frm.dashboard.set_headline(__("To use Google Calendar, enable {0}.", [`<a href='#Form/Google Settings'>${__('Google Settings')}</a>`]));
frm.dashboard.set_headline(__("To use Google Calendar, enable {0}.", [`<a href='/app/Form/Google Settings'>${__('Google Settings')}</a>`]));
}
frappe.realtime.on("import_google_calendar", (data) => {

View file

@ -120,7 +120,7 @@ def authorize_access(g_calendar, reauthorize=None):
frappe.db.commit()
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = "/desk#Form/{0}/{1}".format(quote("Google Calendar"), quote(google_calendar.name))
frappe.local.response["location"] = "/app/Form/{0}/{1}".format(quote("Google Calendar"), quote(google_calendar.name))
frappe.msgprint(_("Google Calendar has been configured."))
except Exception as e:

View file

@ -4,7 +4,7 @@
frappe.ui.form.on('Google Contacts', {
refresh: function(frm) {
if (!frm.doc.enable) {
frm.dashboard.set_headline(__("To use Google Contacts, enable {0}.", [`<a href='#Form/Google Settings'>${__('Google Settings')}</a>`]));
frm.dashboard.set_headline(__("To use Google Contacts, enable {0}.", [`<a href='/app/Form/Google Settings'>${__('Google Settings')}</a>`]));
}
frappe.realtime.on('import_google_contacts', (data) => {

View file

@ -79,7 +79,7 @@ def authorize_access(g_contact, reauthorize=None):
frappe.db.commit()
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = "/desk#Form/Google%20Contacts/{}".format(google_contact.name)
frappe.local.response["location"] = "/app/Form/Google%20Contacts/{}".format(google_contact.name)
frappe.msgprint(_("Google Contacts has been configured."))
except Exception as e:

View file

@ -4,7 +4,7 @@
frappe.ui.form.on('Google Drive', {
refresh: function(frm) {
if (!frm.doc.enable) {
frm.dashboard.set_headline(__("To use Google Drive, enable {0}.", [`<a href='#Form/Google Settings'>${__('Google Settings')}</a>`]));
frm.dashboard.set_headline(__("To use Google Drive, enable {0}.", [`<a href='/app/Form/Google Settings'>${__('Google Settings')}</a>`]));
}
frappe.realtime.on("upload_to_google_drive", (data) => {

View file

@ -88,7 +88,7 @@ def authorize_access(reauthorize=None):
frappe.db.commit()
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = "/desk#Form/{0}".format(quote("Google Drive"))
frappe.local.response["location"] = "/app/Form/{0}".format(quote("Google Drive"))
frappe.msgprint(_("Google Drive has been configured."))
except Exception as e:

View file

@ -287,8 +287,8 @@ def check_if_doc_is_dynamically_linked(doc, method="Delete"):
raise_link_exists_exception(doc, reference_doctype, reference_docname, at_position)
def raise_link_exists_exception(doc, reference_doctype, reference_docname, row=''):
doc_link = '<a href="#Form/{0}/{1}">{1}</a>'.format(doc.doctype, doc.name)
reference_link = '<a href="#Form/{0}/{1}">{1}</a>'.format(reference_doctype, reference_docname)
doc_link = '<a href="/app/Form/{0}/{1}">{1}</a>'.format(doc.doctype, doc.name)
reference_link = '<a href="/app/Form/{0}/{1}">{1}</a>'.format(reference_doctype, reference_docname)
#hack to display Single doctype only once in message
if reference_doctype == reference_docname:

View file

@ -1188,8 +1188,8 @@ class Document(BaseDocument):
doc.set(fieldname, flt(doc.get(fieldname), self.precision(fieldname, doc.parentfield)))
def get_url(self):
"""Returns Desk URL for this document. `/desk#Form/{doctype}/{name}`"""
return "/desk#Form/{doctype}/{name}".format(doctype=self.doctype, name=self.name)
"""Returns Desk URL for this document. `/app/Form/{doctype}/{name}`"""
return "/app/Form/{doctype}/{name}".format(doctype=self.doctype, name=self.name)
def add_comment(self, comment_type='Comment', text=None, comment_email=None, link_doctype=None, link_name=None, comment_by=None):
"""Add a comment to this document.

View file

@ -318,3 +318,4 @@ frappe.patches.v13_0.web_template_set_module #2020-10-05
frappe.patches.v13_0.remove_custom_link
execute:frappe.delete_doc("DocType", "Footer Item")
frappe.patches.v13_0.replace_field_target_with_open_in_new_tab
frappe.core.doctype.role.patches.v13_set_default_desk_properties

View file

@ -73,19 +73,9 @@ frappe.Application = Class.extend({
this.set_rtl();
if (frappe.boot) {
if (localStorage.getItem("session_last_route")) {
window.location.hash = localStorage.getItem("session_last_route");
localStorage.removeItem("session_last_route");
}
}
// page container
this.make_page_container();
// route to home page
frappe.route();
this.set_route();
// trigger app startup
$(document).trigger('startup');
@ -155,7 +145,7 @@ frappe.Application = Class.extend({
});
}, 300000); // check every 5 minutes
if(frappe.user.has_role("System Manager")){
if (frappe.user.has_role("System Manager")) {
setInterval(function() {
frappe.call({
method: 'frappe.core.doctype.log_settings.log_settings.has_unseen_error_log',
@ -164,7 +154,7 @@ frappe.Application = Class.extend({
},
callback: function(r) {
console.log(r);
if(r.message.show_alert){
if (r.message.show_alert) {
frappe.show_alert({
indicator: 'red',
message: r.message.message
@ -179,6 +169,19 @@ frappe.Application = Class.extend({
this.fetch_tags();
},
set_route() {
if (frappe.boot && localStorage.getItem("session_last_route")) {
frappe.set_route(localStorage.getItem("session_last_route"));
localStorage.removeItem("session_last_route");
} else if (frappe._cur_route) {
// go to the appropriate sub-path
frappe.set_route(frappe._cur_route);
} else {
// route to home page
frappe.route();
}
},
setup_frappe_vue() {
Vue.prototype.__ = window.__;
Vue.prototype.frappe = window.frappe;

View file

@ -1007,7 +1007,7 @@ frappe.ui.form.Form = class FrappeForm {
return;
}
frappe.re_route[window.location.hash] = '#Form/' + encodeURIComponent(this.doctype) + '/' + encodeURIComponent(name);
frappe.re_route[frappe.get_sub_path()] = 'Form/' + encodeURIComponent(this.doctype) + '/' + encodeURIComponent(name);
frappe.set_route('Form', this.doctype, name);
}
@ -1522,7 +1522,7 @@ frappe.ui.form.Form = class FrappeForm {
const escaped_name = encodeURIComponent(value);
return repl('<a class="indicator %(color)s" href="#Form/%(doctype)s/%(escaped_name)s" data-doctype="%(doctype)s" data-name="%(name)s">%(label)s</a>', {
return repl('<a class="indicator %(color)s" href="/app/Form/%(doctype)s/%(escaped_name)s" data-doctype="%(doctype)s" data-name="%(name)s">%(label)s</a>', {
color: get_color(doc || {}),
doctype: df.options,
escaped_name: escaped_name,

View file

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

View file

@ -157,7 +157,7 @@ frappe.ui.form.LinkedWith = class LinkedWith {
return `<div class="list-row-container">
<div class="level list-row small">
<div class="level-left bold">
<a href="#Form/${doctype}/${doc.name}">${doc.name}</a>
<a href="/app/Form/${doctype}/${doc.name}">${doc.name}</a>
</div>
</div>
</div>`;

View file

@ -236,7 +236,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog {
${
head ? `<span class="ellipsis text-muted" title="${__(frappe.model.unscrub(column))}">${__(frappe.model.unscrub(column))}</span>`
: (column !== "name" ? `<span class="ellipsis result-row" title="${__(result[column] || '')}">${__(result[column] || '')}</span>`
: `<a href="${"#Form/" + me.doctype + "/" + result[column] || ''}" class="list-id ellipsis" title="${__(result[column] || '')}">
: `<a href="${"/app/Form/" + me.doctype + "/" + result[column] || ''}" class="list-id ellipsis" title="${__(result[column] || '')}">
${__(result[column] || '')}</a>`)}
</div>`;
});

View file

@ -9,7 +9,7 @@
{% if(addr_list[i].is_shipping_address) { %}
<span class="text-muted">({%= __("Shipping") %})</span>{% } %}
<a href="#Form/Address/{%= encodeURIComponent(addr_list[i].name) %}" class="btn btn-default btn-xs pull-right"
<a href="/app/Form/Address/{%= encodeURIComponent(addr_list[i].name) %}" class="btn btn-default btn-xs pull-right"
style="margin-top:-3px; margin-right: -5px;">
{%= __("Edit") %}</a>
</p>

View file

@ -9,7 +9,7 @@
{% if(contact_list[i].designation){ %}
<span class="text-muted">&ndash; {%= contact_list[i].designation %}</span>
{% } %}
<a href="#Form/Contact/{%= encodeURIComponent(contact_list[i].name) %}"
<a href="/app/Form/Contact/{%= encodeURIComponent(contact_list[i].name) %}"
class="btn btn-xs btn-default ml-auto">
{%= __("Edit") %}
</a>

View file

@ -31,7 +31,7 @@
</a>
<ul class="dropdown-menu print-format-dropdown" style="max-height: 300px;
overflow-y: auto; left: auto;">
<li><a class="dropdown-item" href="#Form/Print Settings">
<li><a class="dropdown-item" href="/app/Form/Print Settings">
{%= __("Print Settings") %}</a></li>
<li><a class="btn-printer-setting dropdown-item" style="display: none;">
{%= __("Raw Printing Settings") %}</a></li>

View file

@ -57,14 +57,14 @@ frappe.views.ListFactory = class ListFactory extends frappe.views.Factory {
if (route[0] === 'List' && route.length === 2 && frappe.views.list_view[doctype]) {
if(last_route && last_route[0]==='List' && last_route[1]===doctype) {
// last route same as this route, so going back.
// this happens because #List/Item will redirect to #List/Item/List
// this happens because /app/List/Item will redirect to /app/List/Item/List
// while coming from back button, the last 2 routes will be same, so
// we know user is coming in the reverse direction (via back button)
// example:
// Step 1: #List/Item redirects to #List/Item/List
// Step 2: User hits "back" comes back to #List/Item
// Step 3: Now we cannot send the user back to #List/Item/List so go back one more step
// Step 1: /app/List/Item redirects to /app/List/Item/List
// Step 2: User hits "back" comes back to /app/List/Item
// Step 3: Now we cannot send the user back to /app/List/Item/List so go back one more step
window.history.go(-1);
return true;
} else {

View file

@ -871,7 +871,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
? encodeURIComponent(doc.name)
: doc.name;
return "#Form/" + this.doctype + "/" + docname;
return "/app/Form/" + this.doctype + "/" + docname;
}
get_seen_class(doc) {

View file

@ -10,7 +10,7 @@ Vue.use(VueRouter);
const routes = [
{
name: "recorder-detail",
path: '/desk',
path: '/app',
component: RecorderDetail,
},
{
@ -22,7 +22,7 @@ const routes = [
const router = new VueRouter({
mode: 'history',
base: "/desk#recorder/",
base: "/app/recorder/",
routes: routes,
});

View file

@ -13,39 +13,88 @@ frappe.view_factory = {};
frappe.view_factories = [];
frappe.route_options = null;
frappe.route_hooks = {};
frappe._cur_route = null;
$(window).on('hashchange', function() {
// v1 style routing, route is in hash
if (window.location.hash) {
let sub_path = frappe.get_sub_path(window.location.hash);
window.location.hash = '';
frappe.push_state(sub_path);
}
});
window.addEventListener('popstate', (event) => {
// forward-back button, just re-render based on current route
frappe.route();
});
// routing v2, capture all clicks so that the target is managed with push-state
$('body').on('click', 'a', function(e) {
let override = (e, route) => {
e.preventDefault();
frappe.push_state(frappe.get_sub_path(route));
return false;
};
// click handled, but not by href
if (e.currentTarget.getAttribute('onclick')) return;
const href = e.currentTarget.getAttribute('href');
if (href==='#' || href==='') {
return override(e, '/app');
}
// target has "#" ,this is a v1 style route, so remake it.
if (e.currentTarget.hash) {
return override(e, e.currentTarget.hash);
}
// target has "/app, this is a v2 style route.
if (e.currentTarget.pathname &&
(e.currentTarget.pathname.startsWith('/app') || e.currentTarget.pathname.startsWith('app'))) {
return override(e, e.currentTarget.pathname);
}
});
frappe.route = function() {
// Application is not yet initiated
if (!frappe.app) return;
if(frappe.re_route[window.location.hash] !== undefined) {
let sub_path = frappe.get_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
// now if we try to go back,
// 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.get_route_str(frappe.re_route[window.location.hash]);
var cur_route_val = frappe.get_route_str(frappe._cur_route);
if (decodeURIComponent(re_route_val) === decodeURIComponent(cur_route_val)) {
var re_route_val = frappe.get_sub_path(frappe.re_route[sub_path]);
if (decodeURIComponent(re_route_val) === decodeURIComponent(sub_path)) {
window.history.back();
return;
} else {
window.location.hash = frappe.re_route[window.location.hash];
frappe.set_route(re_route_val);
return;
}
}
frappe._cur_route = window.location.hash;
frappe._cur_route = sub_path;
var route = frappe.get_route();
if (route === false) {
return;
}
let route = frappe.get_route();
frappe.route_history.push(route);
if(route[0]) {
// set title
frappe.route_titles[sub_path] = frappe._original_title || document.title;
// hide open dialog
frappe.ui.hide_open_dialog();
if (route[0]) {
const title_cased_route = frappe.utils.to_title_case(route[0]);
if (title_cased_route === 'Workspace') {
frappe.views.pageview.show('');
@ -66,32 +115,31 @@ frappe.route = function() {
}
}
} else {
// Show desk
// Show home
frappe.views.pageview.show('');
}
if(frappe.route_titles[window.location.hash]) {
frappe.utils.set_title(frappe.route_titles[window.location.hash]);
if (frappe.route_titles[sub_path]) {
frappe.utils.set_title(frappe.route_titles[sub_path]);
} else {
setTimeout(function() {
frappe.route_titles[frappe.get_route_str()] = frappe._original_title || document.title;
}, 1000);
}
if(window.mixpanel) {
window.mixpanel.track(route.slice(0, 2).join(' '));
}
}
frappe.route.trigger('change');
};
frappe.get_route = function(route) {
// for app
route = frappe.get_raw_route_str(route).split('/');
route = frappe.get_sub_path_string(route).split('/');
route = $.map(route, frappe._decode_str);
var parts = null;
var doc_name = route[route.length - 1];
// if the last part contains ? then check if it is valid query string
if(doc_name.indexOf("?") < doc_name.indexOf("=")){
if (doc_name.indexOf("?") < doc_name.indexOf("=")) {
parts = doc_name.split("?");
route[route.length - 1] = parts[0];
} else {
@ -102,17 +150,11 @@ frappe.get_route = function(route) {
frappe.route_options = $.extend(frappe.route_options || {}, query_params);
}
// backward compatibility
if (route && route[0]==='Module') {
frappe.set_route('modules', route[1]);
return false;
}
return route;
}
frappe.get_prev_route = function() {
if(frappe.route_history && frappe.route_history.length > 1) {
if (frappe.route_history && frappe.route_history.length > 1) {
return frappe.route_history[frappe.route_history.length - 2];
} else {
return [];
@ -122,7 +164,7 @@ frappe.get_prev_route = function() {
frappe._decode_str = function(r) {
try {
return decodeURIComponent(r);
} catch(e) {
} catch (e) {
if (e instanceof URIError) {
return r;
} else {
@ -131,31 +173,41 @@ frappe._decode_str = function(r) {
}
}
frappe.get_raw_route_str = function(route) {
if(!route)
frappe.get_sub_path_string = function(route) {
// return clean sub_path from hash or url
// supports both v1 and v2 routing
if (!route) {
route = window.location.hash;
}
if (!route) {
route = window.location.pathname;
}
if(route.substr(0,1)=='#') route = route.substr(1);
if(route.substr(0,1)=='!') route = route.substr(1);
if (route.substr(0, 1)=='/') route = route.substr(1); // for /app/sub
if (route.startsWith('app')) route = route.substr(4); // for desk/sub
if (route.substr(0, 1)=='/') route = route.substr(1);
if (route.substr(0, 1)=='#') route = route.substr(1);
if (route.substr(0, 1)=='!') route = route.substr(1);
return route;
}
};
frappe.get_route_str = function(route) {
var rawRoute = frappe.get_raw_route_str(route);
route = $.map(rawRoute.split('/'), frappe._decode_str).join('/');
frappe.get_sub_path = frappe.get_route_str = function(route) {
var sub_path = frappe.get_sub_path_string(route);
route = $.map(sub_path.split('/'), frappe._decode_str).join('/');
return route;
}
};
frappe.set_route = function() {
return new Promise(resolve => {
var params = arguments;
if(params.length===1 && $.isArray(params[0])) {
if (params.length===1 && $.isArray(params[0])) {
params = params[0];
}
var route = $.map(params, function(a) {
if($.isPlainObject(a)) {
if ($.isPlainObject(a)) {
frappe.route_options = a;
return null;
} else {
@ -174,51 +226,47 @@ frappe.set_route = function() {
url.hash = route;
window.location.replace(url);
} else {
window.location.hash = route;
//
// window.location.hash = route;
// routing v2
frappe.push_state(route);
}
// Set favicon (app.js)
frappe.provide('frappe.app');
frappe.app.set_favicon && frappe.app.set_favicon();
setTimeout(() => {
frappe.after_ajax && frappe.after_ajax(() => {
resolve();
});
}, 100);
});
}
};
frappe.push_state = function (route) {
let url = `/app/${route}`;
if (window.location.pathname !== url) {
// cleanup any remenants of v1 routing
window.location.hash = '';
// push state so the browser looks fine
history.pushState(null, null, url);
// now process the route
frappe.route();
}
};
frappe.set_re_route = function() {
var tmp = window.location.hash;
var tmp = frappe.get_sub_path();
frappe.set_route.apply(null, arguments);
frappe.re_route[tmp] = window.location.hash;
frappe.re_route[tmp] = frappe.get_sub_path();
};
frappe.has_route_options = function() {
return Boolean(Object.keys(frappe.route_options || {}).length);
}
frappe._cur_route = null;
$(window).on('hashchange', function() {
// save the title
frappe.route_titles[frappe._cur_route] = frappe._original_title || document.title;
if(window.location.hash==frappe._cur_route)
return;
// hide open dialog
if(window.cur_dialog) {
if (!cur_dialog.minimizable) {
cur_dialog.hide();
} else if (!cur_dialog.is_minimized) {
cur_dialog.toggle_minimize();
}
}
frappe.route();
frappe.route.trigger('change');
});
};
frappe.utils.make_event_emitter(frappe.route);

View file

@ -227,3 +227,14 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
this.header.find('.modal-title').toggleClass('cursor-pointer');
}
};
frappe.ui.hide_open_dialog = () => {
// hide open dialog
if (window.cur_dialog) {
if (!cur_dialog.minimizable) {
cur_dialog.hide();
} else if (!cur_dialog.is_minimized) {
cur_dialog.toggle_minimize();
}
}
}

View file

@ -121,7 +121,7 @@ frappe.ui.Notifications = class Notifications {
}
route_to_settings() {
frappe.set_route(`#Form/Notification Settings/${frappe.session.user}`);
frappe.set_route(`/app/Form/Notification Settings/${frappe.session.user}`);
}
mark_all_as_read(e) {
@ -253,7 +253,7 @@ class NotificationsView extends BaseNotificaitonsView {
if (this.container.find('.activity-status')) {
this.container.find('.activity-status').replaceWith(
`<a class="recent-item text-center text-muted"
href="#List/Notification Log">
href="/app/List/Notification Log">
<div class="full-log-btn">${__('View Full Log')}</div>
</a>`
);
@ -339,7 +339,7 @@ class NotificationsView extends BaseNotificaitonsView {
this.container.append(this.get_dropdown_item_html(field));
});
this.container.append(`<a class="list-footer"
href="#List/Notification Log">
href="/app/List/Notification Log">
<div class="full-log-btn">${__('See all Activity')}</div>
</a>`);
} else {
@ -449,7 +449,7 @@ class EventsView extends BaseNotificaitonsView {
location = `, ${event.location}`;
}
return `<a class="recent-item event" href="#Form/Event/${event.name}">
return `<a class="recent-item event" href="/app/Form/Event/${event.name}">
<div class="event-border" style="border-color: ${event.color}"></div>
<div class="event-item">
<div class="event-subject">${event.subject}</div>

View file

@ -107,13 +107,7 @@ frappe.search.AwesomeBar = Class.extend({
if(item.onclick) {
item.onclick(item.match);
} else {
var previous_hash = window.location.hash;
frappe.set_route(item.route);
// hashchange didn't fire!
if (window.location.hash == previous_hash) {
frappe.route();
}
}
$input.val("");
});

View file

@ -12,10 +12,7 @@ frappe.ui.toolbar.Toolbar = class {
}));
$('.dropdown-toggle').dropdown();
let awesome_bar = new frappe.search.AwesomeBar();
awesome_bar.setup("#navbar-search");
// awesome_bar.setup("#modal-search");
this.setup_awesomebar();
this.setup_notifications();
this.make();
}
@ -157,8 +154,17 @@ frappe.ui.toolbar.Toolbar = class {
}
}
setup_awesomebar() {
if (frappe.boot.desk_settings.search_bar) {
let awesome_bar = new frappe.search.AwesomeBar();
awesome_bar.setup("#navbar-search");
}
}
setup_notifications () {
this.notifications = new frappe.ui.Notifications();
if (frappe.boot.desk_settings.notifications) {
this.notifications = new frappe.ui.Notifications();
}
}
}

View file

@ -13,7 +13,7 @@ Object.assign(frappe.energy_points, {
const separator = `<span>&nbsp;-&nbsp;</span>`;
const formatted_log = `<span>
<!--${this.get_points(log.points)}&nbsp;-->
<a href="#Form/Energy Point Log/${log.name}">${this.get_form_log_message(log)}</a>
<a href="/app/Form/Energy Point Log/${log.name}">${this.get_form_log_message(log)}</a>
${log.reason ? separator + log.reason: ''}
</span>`;
return formatted_log;

View file

@ -815,7 +815,7 @@ Object.assign(frappe.utils, {
display_text = display_text || name;
doctype = encodeURIComponent(doctype);
name = encodeURIComponent(name);
const route = ['#Form', doctype, name].join('/');
const route = ['/app/Form', doctype, name].join('/');
if (html) {
return `<a href="${route}">${display_text}</a>`;
}

View file

@ -101,7 +101,7 @@ frappe.breadcrumbs = {
label = module_info ? module_info.label : breadcrumbs.module;
if(module_info && !module_info.blocked && frappe.visible_modules.includes(module_info.module_name)) {
$(repl('<li><a href="#workspace/%(module)s">%(label)s</a></li>',
$(repl('<li><a href="/app/workspace/%(module)s">%(label)s</a></li>',
{ module: breadcrumbs.module, label: __(breadcrumbs.module) }))
.appendTo($breadcrumbs);
}
@ -120,7 +120,7 @@ frappe.breadcrumbs = {
} else {
route = 'List/' + breadcrumbs.doctype;
}
$(`<li><a href="#${route}">${doctype}</a></li>`)
$(`<li><a href="/app/${route}">${doctype}</a></li>`)
.appendTo($breadcrumbs)
}
}

View file

@ -82,7 +82,7 @@ frappe.views.Container = Class.extend({
$(document).trigger("page-change");
this.page._route = window.location.hash;
this.page._route = frappe.get_sub_path();
$(this.page).trigger('show');
!this.page.disable_scroll_to_top && frappe.utils.scroll_to(0);
frappe.breadcrumbs.update();

View file

@ -65,7 +65,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView {
frappe.breadcrumbs.add({
type: 'Custom',
label: __('Home'),
route: '#List/File/Home',
route: '/app/List/File/Home',
});
}
@ -322,7 +322,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView {
acc += '/' + curr;
}
return acc;
}, '#List/File');
}, '/app/List/File');
return `<a href="${route}">${folder}</a>`;
})
@ -364,7 +364,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView {
get_route_url(file) {
return file.is_folder
? '#List/File/' + file.name
? '/app/List/File/' + file.name
: this.get_form_link(file);
}

View file

@ -8,7 +8,7 @@
<div class="image-view-body">
<a data-name="{{ data.name }}"
title="{{ data.name }}"
href="#Form/{{ data.doctype }}/{{ data.name }}"
href="/app/Form/{{ data.doctype }}/{{ data.name }}"
>
<div class="image-field"
data-name="{{ data.name }}"

View file

@ -328,7 +328,7 @@ frappe.views.KanbanView.show_kanban_dialog = function (doctype, show_existing) {
<p class="text-medium">
${__('No fields found that can be used as a Kanban Column. Use the Customize Form to add a Custom Field of type "Select".')}
</p>
<a class="btn btn-xs btn-default" href="#Form/Customize Form?doc_type=${doctype}">
<a class="btn btn-xs btn-default" href="/app/Form/Customize Form?doc_type=${doctype}">
${__('Customize Form')}
</a>
</div>

View file

@ -38,15 +38,15 @@ frappe.views.pageview = {
},
show: function(name) {
if(!name) {
if (!name) {
name = (frappe.boot ? frappe.boot.home_page : window.page_name);
}
frappe.model.with_doctype("Page", function() {
frappe.views.pageview.with_page(name, function(r) {
if(r && r.exc) {
if(!r['403'])
if (r && r.exc) {
if (!r['403'])
frappe.show_not_found(name);
} else if(!frappe.pages[name]) {
} else if (!frappe.pages[name]) {
new frappe.views.Page(name);
}
frappe.container.change_to(name);
@ -59,7 +59,7 @@ frappe.views.Page = class Page {
constructor(name) {
this.name = name;
var me = this;
// web home page
if(name==window.page_name) {
this.wrapper = document.getElementById('page-' + name);
@ -87,7 +87,7 @@ frappe.views.Page = class Page {
}
this.trigger_page_event('on_page_load');
// set events
$(this.wrapper).on('show', function() {
window.cur_frm = null;

View file

@ -677,7 +677,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
<span class="indicator orange">
${part1}
${part2}
<a href="#List/Prepared%20Report?report_name=${this.report_name}">${part3}</a>
<a href="/app/List/Prepared%20Report?report_name=${this.report_name}">${part3}</a>
</span>
`);
};
@ -754,7 +754,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
get_queued_prepared_reports_warning_message(reports) {
const route = `#List/Prepared Report/List?status=Queued&report_name=${this.report_name}`;
const route = `/app/List/Prepared Report/List?status=Queued&report_name=${this.report_name}`;
const report_link_html = reports.length == 1
? `<a class="underline" href="${route}">${__('1 Report')}</a>`
: `<a class="underline" href="${route}">${__("{0} Reports", [reports.length])}</a>`;
@ -769,7 +769,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
${no_of_reports_html}
</p>`;
let get_item_html = item => `<a class="underline" href="#Form/Prepared Report/${item.name}">${item.name}</a>`;
let get_item_html = item => `<a class="underline" href="/app/Form/Prepared Report/${item.name}">${item.name}</a>`;
warning_message += reports.map(get_item_html).join(', ');
@ -805,7 +805,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
// Rememeber the name of Prepared Report doc
this.prepared_report_doc_name = data.name;
let alert_message = `Report initiated. You can track its status
<a class="bold" href='#Form/Prepared Report/${data.name}'>here</a>`;
<a class="bold" href='/app/Form/Prepared Report/${data.name}'>here</a>`;
frappe.show_alert({message: alert_message, indicator: 'orange'}, 10);
this.toggle_nothing_to_show(true);
});

View file

@ -93,7 +93,7 @@ frappe.views.Workspace = class Workspace {
const get_sidebar_item = function (item) {
return $(`<a
href="${"desk#workspace/" + item.name}"
href="/desk/workspace/${item.name}"
class="desk-sidebar-item standard-sidebar-item ${item.selected ? "selected" : ""}"
>
<div> ${frappe.utils.icon(item.icon || "folder-normal", "md")} </div>

View file

@ -64,7 +64,7 @@ export default class OnboardingWidget extends Widget {
"Watch Video": (step) => this.show_video(step),
"Create Entry": (step) => {
if (step.is_complete) {
frappe.set_route(`#List/${step.reference_document}`);
frappe.set_route(`/app/List/${step.reference_document}`);
} else {
if (step.show_full_form) {
this.create_entry(step);

View file

@ -55,7 +55,6 @@ function generate_route(item) {
route = "dashboard/" + item.name;
}
route = "#" + route;
} else {
route = item.route;
}

View file

@ -38,6 +38,6 @@ frappe.ui.form.on('Energy Point Log', {
make_reference_name_link(frm) {
let dt = frm.doc.reference_doctype;
let dn = frm.doc.reference_name;
frm.fields_dict.reference_name.$input_wrapper.find('.control-value').wrapInner(`<a href='#Form/${dt}/${dn}'></a>`);
frm.fields_dict.reference_name.$input_wrapper.find('.control-value').wrapInner(`<a href='/app/Form/${dt}/${dn}'></a>`);
}
});

View file

@ -49,7 +49,7 @@ def add_comment(comment, comment_email, comment_by, reference_doctype, reference
clear_cache(route)
content = (comment.content
+ "<p><a href='{0}/desk#Form/Comment/{1}' style='font-size: 80%'>{2}</a></p>".format(frappe.utils.get_request_site_address(),
+ "<p><a href='{0}/app/Form/Comment/{1}' style='font-size: 80%'>{2}</a></p>".format(frappe.utils.get_request_site_address(),
comment.name,
_("View Comment")))

View file

@ -16,7 +16,7 @@
</a>
{%- endif -%}
{%- endfor -%}
<a class="dropdown-item switch-to-desk hidden" href="/desk">{{ _('Switch To Desk') }}</a>
<a class="dropdown-item switch-to-desk hidden" href="/app">{{ _('Switch To Desk') }}</a>
</ul>
</li>
{% endif %}

View file

@ -9,7 +9,7 @@
{{ _("Success") }}</span>
</div>
<p>{{ _("Your connection request to Google Calendar was successfully accepted") }}</p>
<div><a href='{{ "/desk" }}' class='btn btn-primary btn-sm'>
<div><a href='{{ "/app" }}' class='btn btn-primary btn-sm'>
{{ _("Back to Desk") }}</a></div>
</div>
<style>

View file

@ -40,7 +40,7 @@ class BotParser(object):
def format_list(self, data):
'''Format list as markdown'''
return _('I found these: ') + ', '.join([' [{title}](#Form/{doctype}/{name})'.format(
return _('I found these: ') + ', '.join([' [{title}](/app/Form/{doctype}/{name})'.format(
title = d.title or d.name,
doctype=self.get_doctype(),
name=d.name) for d in data])
@ -58,7 +58,7 @@ class ShowNotificationBot(BotParser):
if open_items:
return ("Following items need your attention:\n\n"
+ "\n\n".join(["{0} [{1}](#List/{1})".format(d[1], d[0])
+ "\n\n".join(["{0} [{1}](/app/List/{1})".format(d[1], d[0])
for d in open_items if d[1] > 0]))
else:
return 'Take it easy, nothing urgent needs your attention'
@ -77,7 +77,7 @@ class GetOpenListBot(BotParser):
else:
data = [{'name':d[0], 'title':d[1]} for d in frappe.get_attr(filters)(as_list=True)]
return ", ".join('[{title}](#Form/{doctype}/{name})'.format(doctype=doctype,
return ", ".join('[{title}](/app/Form/{doctype}/{name})'.format(doctype=doctype,
name=d.get('name'), title=d.get('title') or d.get('name')) for d in data)
else:
return _("Can't identify open {0}. Try something else.").format(doctype)

View file

@ -172,7 +172,7 @@ def import_doc(d, doctype, overwrite, row_idx, submit=False, ignore_links=False)
doc.get('name')))
def getlink(doctype, name):
return '<a href="#Form/%(doctype)s/%(name)s">%(name)s</a>' % locals()
return '<a href="/app/Form/%(doctype)s/%(name)s">%(name)s</a>' % locals()
def get_csv_content_from_google_sheets(url):
# https://docs.google.com/spreadsheets/d/{sheetid}}/edit#gid={gid}

View file

@ -1035,13 +1035,13 @@ def get_link_to_report(name, label=None, report_type=None, doctype=None, filters
return """<a href='{0}'>{1}</a>""".format(get_url_to_report(name, report_type, doctype), label)
def get_absolute_url(doctype, name):
return "desk#Form/{0}/{1}".format(quoted(doctype), quoted(name))
return "desk/app/Form/{0}/{1}".format(quoted(doctype), quoted(name))
def get_url_to_form(doctype, name):
return get_url(uri = "desk#Form/{0}/{1}".format(quoted(doctype), quoted(name)))
return get_url(uri = "desk/app/Form/{0}/{1}".format(quoted(doctype), quoted(name)))
def get_url_to_list(doctype):
return get_url(uri = "desk#List/{0}".format(quoted(doctype)))
return get_url(uri = "desk/app/List/{0}".format(quoted(doctype)))
def get_url_to_report(name, report_type = None, doctype = None):
if report_type == "Report Builder":

View file

@ -307,7 +307,7 @@ def redirect_post_login(desk_user, redirect_to=None, provider=None):
if not redirect_to:
# the #desktop is added to prevent a facebook redirect bug
desk_uri = "/desk#workspace" if provider == 'facebook' else '/desk'
desk_uri = "/app/workspace" if provider == 'facebook' else '/app'
redirect_to = desk_uri if desk_user else "/me"
redirect_to = frappe.utils.get_url(redirect_to)

View file

@ -44,7 +44,7 @@ def authorize_access(reauthorize=None):
frappe.db.commit()
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = "/desk#Form/{0}".format(quote("Website Settings"))
frappe.local.response["location"] = "/app/Form/{0}".format(quote("Website Settings"))
frappe.msgprint(_("Google Indexing has been configured."))
except Exception as e:

View file

@ -252,6 +252,9 @@ def resolve_path(path):
if path != "index":
path = resolve_from_map(path)
if path.startswith("app"):
path = "app"
return path
def resolve_from_map(path):

View file

@ -13,7 +13,7 @@ frappe.listview_settings['Workflow Action'] = {
? encodeURIComponent(docname)
: docname;
const link = '#Form/' + doctype + '/' + docname;
const link = '/app/Form/' + doctype + '/' + docname;
return link;
}
};

View file

@ -4,7 +4,7 @@
from __future__ import unicode_literals, print_function
no_cache = 1
base_template_path = "templates/www/desk.html"
base_template_path = "templates/www/app.html"
import os, re
import frappe
@ -14,8 +14,8 @@ import frappe.sessions
def get_context(context):
if frappe.session.user == "Guest":
frappe.throw(_("Log in to access this page."), frappe.PermissionError)
elif frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User":
frappe.throw(_("You are not permitted to access this page."), frappe.PermissionError)
# elif frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User":
# frappe.throw(_("You are not permitted to access this page."), frappe.PermissionError)
hooks = frappe.get_hooks()
try:

View file

@ -24,7 +24,7 @@ def get_context(context):
if frappe.session.data.user_type=="Website User":
redirect_to = get_home_page()
else:
redirect_to = "/desk"
redirect_to = "/app"
frappe.local.flags.redirect_location = redirect_to
raise frappe.Redirect