diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml index a0f77b43fd..80b2469c0c 100644 --- a/.github/workflows/docs-checker.yml +++ b/.github/workflows/docs-checker.yml @@ -13,7 +13,7 @@ jobs: steps: - name: 'Setup Environment' - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.8 diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 443ee45bf7..7a17648ff0 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v2 - name: Set up Python 3.8 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.8 diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index 7dffc30dc0..ae5802a8e1 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -31,12 +31,12 @@ jobs: uses: actions/checkout@v2 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.9' - name: Setup Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: 14 check-latest: true @@ -56,7 +56,7 @@ jobs: - name: Cache pip if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }} @@ -66,7 +66,7 @@ jobs: - name: Cache node modules if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-node-modules with: @@ -82,7 +82,7 @@ jobs: id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2 + - uses: actions/cache@v3 if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache with: diff --git a/.github/workflows/publish-assets-develop.yml b/.github/workflows/publish-assets-develop.yml index f56d1460b5..6cd3af78f2 100644 --- a/.github/workflows/publish-assets-develop.yml +++ b/.github/workflows/publish-assets-develop.yml @@ -13,10 +13,10 @@ jobs: - uses: actions/checkout@v2 with: path: 'frappe' - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v3 with: node-version: 14 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: '3.9' - name: Set up bench and build assets diff --git a/.github/workflows/publish-assets-releases.yml b/.github/workflows/publish-assets-releases.yml index faf41ed23e..f242a363b4 100644 --- a/.github/workflows/publish-assets-releases.yml +++ b/.github/workflows/publish-assets-releases.yml @@ -16,10 +16,10 @@ jobs: - uses: actions/checkout@v2 with: path: 'frappe' - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v3 with: python-version: '12.x' - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: '3.9' - name: Set up bench and build assets diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e9936482b0..32c5d53831 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Setup Node.js v14 - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: 14 - name: Setup dependencies diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 33fc221e80..f7f3aae8d0 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -40,7 +40,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.9' @@ -53,7 +53,7 @@ jobs: PR_NUMBER: ${{ github.event.number }} REPO_NAME: ${{ github.repository }} - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 if: ${{ steps.check-build.outputs.build == 'strawberry' }} with: node-version: 14 @@ -67,7 +67,7 @@ jobs: - name: Cache pip if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }} @@ -77,7 +77,7 @@ jobs: - name: Cache node modules if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-node-modules with: @@ -93,7 +93,7 @@ jobs: id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2 + - uses: actions/cache@v3 if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache with: @@ -126,7 +126,7 @@ jobs: - name: Upload coverage data if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: name: MariaDB fail_ci_if_error: true diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index 2b4a2edae0..5116d4198e 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.9' @@ -56,7 +56,7 @@ jobs: PR_NUMBER: ${{ github.event.number }} REPO_NAME: ${{ github.repository }} - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 if: ${{ steps.check-build.outputs.build == 'strawberry' }} with: node-version: '14' @@ -70,7 +70,7 @@ jobs: - name: Cache pip if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }} @@ -80,7 +80,7 @@ jobs: - name: Cache node modules if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-node-modules with: @@ -96,7 +96,7 @@ jobs: id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2 + - uses: actions/cache@v3 if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache with: @@ -129,7 +129,7 @@ jobs: - name: Upload coverage data if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: name: Postgres fail_ci_if_error: true diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 3518608f33..72fe3e67bb 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -39,7 +39,7 @@ jobs: uses: actions/checkout@v2 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.9' @@ -52,7 +52,7 @@ jobs: PR_NUMBER: ${{ github.event.number }} REPO_NAME: ${{ github.repository }} - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 if: ${{ steps.check-build.outputs.build == 'strawberry' }} with: node-version: 14 @@ -66,7 +66,7 @@ jobs: - name: Cache pip if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }} @@ -76,7 +76,7 @@ jobs: - name: Cache node modules if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-node-modules with: @@ -92,7 +92,7 @@ jobs: id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2 + - uses: actions/cache@v3 if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache with: @@ -103,7 +103,7 @@ jobs: - name: Cache cypress binary if: ${{ steps.check-build.outputs.build == 'strawberry' }} - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache key: ${{ runner.os }}-cypress- @@ -158,7 +158,7 @@ jobs: - name: Upload Coverage Data if: ${{ steps.check-build.outputs.build == 'strawberry' && steps.check_coverage.outputs.files_exists == 'true' }} - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: name: Cypress fail_ci_if_error: true @@ -168,7 +168,7 @@ jobs: - name: Upload Server Coverage Data if: ${{ steps.check-build.outputs.build-server == 'strawberry' }} - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: name: MariaDB fail_ci_if_error: true diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index e62ba6bec5..938034a34a 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -28,6 +28,7 @@ context('Awesome Bar', () => { cy.findByPlaceholderText('ID') .should('have.value', '%test%'); + cy.clear_filters(); }); it('navigates to new form', () => { diff --git a/cypress/integration/routing.js b/cypress/integration/routing.js new file mode 100644 index 0000000000..0822dd9b7d --- /dev/null +++ b/cypress/integration/routing.js @@ -0,0 +1,40 @@ +const list_view = "/app/todo"; + +// test round trip with filter types + +const test_queries = [ + "?status=Open", + `?date=%5B"Between"%2C%5B"2022-06-01"%2C"2022-06-30"%5D%5D`, + `?date=%5B">"%2C"2022-06-01"%5D`, + `?name=%5B"like"%2C"%2542%25"%5D`, + `?status=%5B"not%20in"%2C%5B"Open"%2C"Closed"%5D%5D`, +]; + +describe("SPA Routing", { scrollBehavior: false }, () => { + before(() => { + cy.login(); + cy.go_to_list("ToDo"); + }); + + after(() => { + cy.clear_filters(); // avoid flake in future tests + }); + + it("should apply filter on list view from route", () => { + test_queries.forEach((query) => { + const full_url = `${list_view}${query}`; + cy.visit(full_url); + cy.findByTitle("To Do").should("exist"); + + const expected = new URLSearchParams(query); + cy.location().then((loc) => { + const actual = new URLSearchParams(loc.search); + // This might appear like a dumb test checking visited URL to itself + // but it's actually doing a round trip + // URL with params -> parsed filters -> new URL + // if it's same that means everything worked in between. + expect(actual.toString()).to.eq(expected.toString()); + }); + }); + }); +}); diff --git a/frappe/__init__.py b/frappe/__init__.py index 45c5ca500e..cd7a1f22ee 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -927,7 +927,7 @@ def has_permission( if throw and not out: # mimics frappe.throw - document_label = f"{doc.doctype} {doc.name}" if doc else doctype + document_label = f"{_(doc.doctype)} {doc.name}" if doc else _(doctype) msgprint( _("No permission for {0}").format(document_label), raise_exception=ValidationError, diff --git a/frappe/client.py b/frappe/client.py index 4afe0898bc..154fcf31e2 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -101,7 +101,7 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren check_parent_permission(parent, doctype) if not frappe.has_permission(doctype, parent_doctype=parent): - frappe.throw(_("No permission for {0}").format(doctype), frappe.PermissionError) + frappe.throw(_("No permission for {0}").format(_(doctype)), frappe.PermissionError) filters = get_safe_filters(filters) if isinstance(filters, str): @@ -143,7 +143,7 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren @frappe.whitelist() def get_single_value(doctype, field): if not frappe.has_permission(doctype): - frappe.throw(_("No permission for {0}").format(doctype), frappe.PermissionError) + frappe.throw(_("No permission for {0}").format(_(doctype)), frappe.PermissionError) value = frappe.db.get_single_value(doctype, field) return value diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index a0f742c55a..1c5803ffea 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -170,12 +170,13 @@ def delete_contact_and_address(doctype, docname): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -def filter_dynamic_link_doctypes(txt: str, filters: Dict) -> List[List[str]]: +def filter_dynamic_link_doctypes( + doctype, txt: str, searchfield, start, page_len, filters: Dict +) -> List[List[str]]: from frappe.permissions import get_doctypes_with_read txt = txt or "" filters = filters or {} - TXT_PATTERN = re.compile(f"{txt}.*") _doctypes_from_df = frappe.get_all( "DocField", @@ -184,13 +185,13 @@ def filter_dynamic_link_doctypes(txt: str, filters: Dict) -> List[List[str]]: distinct=True, order_by=None, ) - doctypes_from_df = {d for d in _doctypes_from_df if TXT_PATTERN.search(_(d), re.IGNORECASE)} + doctypes_from_df = {d for d in _doctypes_from_df if txt.lower() in _(d).lower()} filters.update({"dt": ("not in", doctypes_from_df)}) _doctypes_from_cdf = frappe.get_all( "Custom Field", filters=filters, pluck="dt", distinct=True, order_by=None ) - doctypes_from_cdf = {d for d in _doctypes_from_cdf if TXT_PATTERN.search(_(d), re.IGNORECASE)} + doctypes_from_cdf = {d for d in _doctypes_from_cdf if txt.lower() in _(d).lower()} all_doctypes = doctypes_from_df.union(doctypes_from_cdf) allowed_doctypes = set(get_doctypes_with_read()) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index dfe5304a87..8a5becf3cc 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -743,7 +743,7 @@ class DatabaseQuery(object): ): only_if_shared = True if not self.shared: - frappe.throw(_("No permission to read {0}").format(self.doctype), frappe.PermissionError) + frappe.throw(_("No permission to read {0}").format(_(self.doctype)), frappe.PermissionError) else: self.conditions.append(self.get_share_condition()) diff --git a/frappe/public/js/form.bundle.js b/frappe/public/js/form.bundle.js index 2719535599..38d76f1f26 100644 --- a/frappe/public/js/form.bundle.js +++ b/frappe/public/js/form.bundle.js @@ -10,7 +10,6 @@ import "./frappe/form/templates/set_sharing.html"; import "./frappe/form/templates/timeline_message_box.html"; import "./frappe/form/templates/users_in_sidebar.html"; -import "./frappe/form/controls/control.js"; import "./frappe/views/formview.js"; import "./frappe/form/form.js"; import "./frappe/meta_tag.js"; diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index eefc629b4d..13d61d689b 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -575,8 +575,6 @@ frappe.ui.form.Form = class FrappeForm { this.$wrapper.trigger('render_complete'); - this.layout.set_first_tab_as_active(switched || this.cscript.is_onload); - if(!this.hidden) { this.layout.show_empty_form_message(); } @@ -1842,6 +1840,15 @@ frappe.ui.form.Form = class FrappeForm { }); }); } + set_active_tab(tab) { + if (!this.active_tab_map) { + this.active_tab_map = {}; + } + this.active_tab_map[this.docname] = tab; + } + get_active_tab() { + return this.active_tab_map && this.active_tab_map[this.docname]; + } }; frappe.validated = 0; diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 080f8e0180..add9ce7b8b 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -123,7 +123,7 @@ frappe.ui.form.Layout = class Layout { if (this.is_tabbed_layout()) { // add a tab without `fieldname` to avoid conflicts - let default_tab = {label: __('Details'), fieldtype: "Tab Break"}; + let default_tab = {label: __('Details'), fieldtype: "Tab Break", fieldname: "__details"}; let first_tab = this.fields[1].fieldtype === "Tab Break" ? this.fields[1] : null; if (!first_tab) { this.fields.splice(1, 0, default_tab); @@ -336,12 +336,17 @@ frappe.ui.form.Layout = class Layout { if (visible_tabs && visible_tabs.length == 1) { visible_tabs[0].parent.toggleClass('hide show'); } + this.set_tab_as_active(); } - set_first_tab_as_active(switched) { - if (this.tabs.length && (switched || !this.frm.active_tab)) { + set_tab_as_active() { + let frm_active_tab = this?.frm.get_active_tab?.(); + if (frm_active_tab) { + frm_active_tab.set_active(); + } else if (this.tabs.length) { // set first tab as active when opening for first time, or new doc - this.tabs[0].set_active(); + let first_visible_tab = this.tabs.find(tab => !tab.is_hidden()); + first_visible_tab && first_visible_tab.set_active(); } } diff --git a/frappe/public/js/frappe/form/tab.js b/frappe/public/js/frappe/form/tab.js index 69c573186b..324d0c50c8 100644 --- a/frappe/public/js/frappe/form/tab.js +++ b/frappe/public/js/frappe/form/tab.js @@ -10,6 +10,7 @@ export default class Tab { this.fields_list = []; this.fields_dict = {}; this.make(); + this.setup_listeners(); this.refresh(); } @@ -79,7 +80,6 @@ export default class Tab { set_active() { this.parent.find('.nav-link').tab('show'); this.wrapper.addClass('active'); - this.frm.active_tab = this; } is_active() { @@ -87,7 +87,12 @@ export default class Tab { } is_hidden() { - this.wrapper.hasClass('hide') - && this.parent.hasClass('hide'); + return this.wrapper.hasClass('hide'); + } + + setup_listeners() { + this.parent.find('.nav-link').on('shown.bs.tab', () => { + this?.frm.set_active_tab?.(this); + }); } } diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index b91693257a..bbee90048b 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -621,6 +621,8 @@ class FilterArea { filters = filters.filter(f => !this.exists(f)); + // standard filters = filters visible on list view + // non-standard filters = filters set by filter button const { non_standard_filters, promise } = this.set_standard_filter( filters ); @@ -680,9 +682,14 @@ class FilterArea { out.promise = out.promise || Promise.resolve(); out.non_standard_filters = out.non_standard_filters || []; + // set in list view area if filters are present + // don't set like filter on link fields (gets reset) if ( fields_dict[fieldname] && - (condition === "=" || condition === "like") + ( + condition === "=" || + (condition === "like" && fields_dict[fieldname]?.df?.fieldtype != "Link") + ) ) { // standard filter out.promise = out.promise.then(() => diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index d57c57a0b6..3a39a949c9 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -291,6 +291,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { super.refresh().then(() => { this.render_header(refresh_header); this.update_checkbox(); + this.update_url_with_filters(); }); } @@ -1454,12 +1455,15 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { on_update() {} - get_share_url() { + update_url_with_filters() { + window.history.replaceState(null, null, this.get_url_with_filters()); + } + + get_url_with_filters() { const query_params = this.get_filters_for_args() .map((filter) => { - filter[3] = encodeURIComponent(filter[3]); if (filter[2] === "=") { - return `${filter[1]}=${filter[3]}`; + return `${filter[1]}=${encodeURIComponent(filter[3])}`; } return [ filter[1], @@ -1469,34 +1473,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }) .join("&"); - let full_url = window.location.href; + let full_url = window.location.href.replace(window.location.search, ""); if (query_params) { full_url += "?" + query_params; } return full_url; } - share_url() { - const d = new frappe.ui.Dialog({ - title: __("Share URL"), - fields: [ - { - fieldtype: "Code", - fieldname: "url", - label: "URL", - default: this.get_share_url(), - read_only: 1, - }, - ], - primary_action_label: __("Copy to clipboard"), - primary_action: () => { - frappe.utils.copy_to_clipboard(this.get_share_url()); - d.hide(); - }, - }); - d.show(); - } - get_menu_items() { const doctype = this.doctype; const items = []; @@ -1561,13 +1544,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { shortcut: "Ctrl+K", }); - items.push({ - label: __("Share URL", null, "Button in list view menu"), - action: () => this.share_url(), - standard: true, - shortcut: "Ctrl+L", - }); - if ( frappe.user.has_role("System Manager") && frappe.boot.developer_mode === 1 diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 39e6555654..a6bff4d72f 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -121,7 +121,7 @@ frappe.router = { route = this.get_sub_path_string(route).split('/'); if (!route) return []; route = $.map(route, this.decode_component); - this.set_route_options_from_url(route); + this.set_route_options_from_url(); return this.convert_to_standard_route(route); }, @@ -410,18 +410,17 @@ frappe.router = { return route; }, - set_route_options_from_url(route) { + set_route_options_from_url() { // set query parameters as frappe.route_options - var last_part = route[route.length - 1]; - if (last_part.indexOf("?") < last_part.indexOf("=")) { - // has ? followed by = - let parts = last_part.split("?"); + let query_string = window.location.search; - // route should not contain string after ? - route[route.length - 1] = parts[0]; + if (!frappe.route_options) { + frappe.route_options = {}; + } - let query_params = frappe.utils.get_query_params(parts[1]); - frappe.route_options = $.extend(frappe.route_options || {}, query_params); + let params = new URLSearchParams(query_string); + for (const [key, value] of params) { + frappe.route_options[key] = value; } }, diff --git a/frappe/public/js/frappe/views/kanban/kanban_view.js b/frappe/public/js/frappe/views/kanban/kanban_view.js index b546365fd9..129db13b07 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_view.js +++ b/frappe/public/js/frappe/views/kanban/kanban_view.js @@ -64,10 +64,6 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { }); } - before_refresh() { - - } - setup_page() { this.hide_sidebar = true; this.hide_page_form = true; @@ -154,7 +150,10 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { get_card_meta() { var meta = frappe.get_meta(this.doctype); + // preserve route options erased by new doc + let route_options = {...frappe.route_options}; var doc = frappe.model.get_new_doc(this.doctype); + frappe.route_options = route_options; var title_field = null; var quick_entry = false; diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 7d00f3ed13..ac7f8327ab 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -57,6 +57,30 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.menu_items = []; } + update_url_with_filters() { + window.history.replaceState(null, null, this.get_url_with_filters()); + } + + get_url_with_filters() { + const query_params = Object.entries(this.get_filter_values()) + .map(([field, value], _idx) => { + // multiselects + if (Array.isArray(value)) { + if (!value.length) return ''; + value = JSON.stringify(value); + } + return `${field}=${encodeURIComponent(value)}`; + }) + .filter(Boolean) + .join("&"); + + let full_url = window.location.href.replace(window.location.search, ""); + if (query_params) { + full_url += "?" + query_params; + } + return full_url; + } + set_default_secondary_action() { this.refresh_button && this.refresh_button.remove(); this.refresh_button = this.page.add_action_icon("refresh", () => { @@ -538,7 +562,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { const promises = filters_to_set.map(f => { return () => { - const value = route_options[f.df.fieldname]; + let value = route_options[f.df.fieldname]; + if (typeof value === 'string' && value[0] === '[') { + // multiselect array + value = JSON.parse(value); + } f.set_value(value); }; }); @@ -652,6 +680,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { frappe.hide_progress(); }).finally(() => { this.hide_loading_screen(); + this.update_url_with_filters(); }); } diff --git a/frappe/share.py b/frappe/share.py index 3edcb1be38..dfb4836995 100644 --- a/frappe/share.py +++ b/frappe/share.py @@ -175,7 +175,7 @@ def check_share_permission(doctype, name): """Check if the user can share with other users""" if not frappe.has_permission(doctype, ptype="share", doc=name): frappe.throw( - _("No permission to {0} {1} {2}").format("share", doctype, name), frappe.PermissionError + _("No permission to {0} {1} {2}").format("share", _(doctype), name), frappe.PermissionError ) @@ -190,7 +190,7 @@ def notify_assignment(shared_by, doctype, doc_name, everyone, notify=0): reference_user = get_fullname(frappe.session.user) notification_message = _("{0} shared a document {1} {2} with you").format( - frappe.bold(reference_user), frappe.bold(doctype), get_title_html(title) + frappe.bold(reference_user), frappe.bold(_(doctype)), get_title_html(title) ) notification_doc = {