diff --git a/ts/WoltLabSuite/Core/Component/GridView/State.ts b/ts/WoltLabSuite/Core/Component/GridView/State.ts index 6ccf308263..19d6c7f26c 100644 --- a/ts/WoltLabSuite/Core/Component/GridView/State.ts +++ b/ts/WoltLabSuite/Core/Component/GridView/State.ts @@ -25,6 +25,7 @@ export class State extends EventTarget { readonly #selection: Selection; readonly #sorting: Sorting; readonly #gridViewFooter: HTMLElement; + #lastUrl: string; #pageNo: number; constructor( @@ -75,6 +76,7 @@ export class State extends EventTarget { }); } + this.#updateLastUrl(); this.#updatePaginationUrl(); this.#updateGridViewFooter(); } @@ -104,6 +106,7 @@ export class State extends EventTarget { this.#pagination.count = count; this.#updatePaginationUrl(); this.#selection.refresh(); + this.#updateLastUrl(); if (cause === StateChangeCause.Change || cause === StateChangeCause.Pagination) { this.#updateQueryString(); @@ -124,26 +127,7 @@ export class State extends EventTarget { return; } - const url = new URL(this.#baseUrl); - - const parameters: [string, string][] = []; - if (this.#pageNo > 1) { - parameters.push(["pageNo", this.#pageNo.toString()]); - } - - for (const parameter of this.#sorting.getQueryParameters()) { - parameters.push(parameter); - } - - for (const parameter of this.#filter.getQueryParameters()) { - parameters.push(parameter); - } - - if (parameters.length > 0) { - url.search += url.search !== "" ? "&" : "?"; - url.search += new URLSearchParams(parameters).toString(); - } - + const url = this.#getUrl(); window.history.pushState({}, document.title, url.toString()); } @@ -152,9 +136,18 @@ export class State extends EventTarget { return; } + const url = this.#getUrl(false); + this.#pagination.url = url.toString(); + } + + #getUrl(withPageNo: boolean = true): URL { const url = new URL(this.#baseUrl); const parameters: [string, string][] = []; + if (withPageNo && this.#pageNo > 1) { + parameters.push(["pageNo", this.#pageNo.toString()]); + } + for (const parameter of this.#sorting.getQueryParameters()) { parameters.push(parameter); } @@ -168,14 +161,19 @@ export class State extends EventTarget { url.search += new URLSearchParams(parameters).toString(); } - this.#pagination.url = url.toString(); + return url; } #handlePopState(): void { + const url = new URL(window.location.href); + + if (this.#isOnlyHashChange(url)) { + return; + } + let pageNo = 1; - const { searchParams } = new URL(window.location.href); - const value = searchParams.get("pageNo"); + const value = url.searchParams.get("pageNo"); if (value !== null) { pageNo = parseInt(value); if (Number.isNaN(pageNo) || pageNo < 1) { @@ -183,12 +181,25 @@ export class State extends EventTarget { } } - this.#filter.updateFromSearchParams(searchParams); - this.#sorting.updateFromSearchParams(searchParams); + this.#filter.updateFromSearchParams(url.searchParams); + this.#sorting.updateFromSearchParams(url.searchParams); this.#switchPage(pageNo, StateChangeCause.History); } + #updateLastUrl(): void { + if (!this.#baseUrl) { + return; + } + + const url = this.#getUrl(); + this.#lastUrl = url.pathname + url.search; + } + + #isOnlyHashChange(url: URL): boolean { + return url.pathname + url.search === this.#lastUrl; + } + #updateGridViewFooter(): void { const hasPagination = this.#pagination.count > 1; this.#gridViewFooter.hidden = !hasPagination && !this.#selection.selectionBarVisible(); diff --git a/ts/WoltLabSuite/Core/Component/ListView/State.ts b/ts/WoltLabSuite/Core/Component/ListView/State.ts index 3fb05cb885..b876403ded 100644 --- a/ts/WoltLabSuite/Core/Component/ListView/State.ts +++ b/ts/WoltLabSuite/Core/Component/ListView/State.ts @@ -28,6 +28,7 @@ export class State extends EventTarget { readonly #selection: Selection; readonly #sorting: Sorting; readonly #listViewFooter: HTMLElement; + #lastUrl: string; #pageNo: number; constructor( @@ -96,6 +97,7 @@ export class State extends EventTarget { ); }); + this.#updateLastUrl(); this.#updatePaginationUrl(); this.#updateListViewFooter(); } @@ -125,6 +127,7 @@ export class State extends EventTarget { this.#pagination.count = count; this.#updatePaginationUrl(); this.#selection.refresh(); + this.#updateLastUrl(); if (cause === StateChangeCause.Change || cause === StateChangeCause.Pagination) { this.#updateQueryString(); @@ -145,26 +148,7 @@ export class State extends EventTarget { return; } - const url = new URL(this.#baseUrl); - - const parameters: [string, string][] = []; - if (this.#pageNo > 1) { - parameters.push(["pageNo", this.#pageNo.toString()]); - } - - for (const parameter of this.#sorting.getQueryParameters()) { - parameters.push(parameter); - } - - for (const parameter of this.#filter.getQueryParameters()) { - parameters.push(parameter); - } - - if (parameters.length > 0) { - url.search += url.search !== "" ? "&" : "?"; - url.search += new URLSearchParams(parameters).toString(); - } - + const url = this.#getUrl(); window.history.pushState({}, document.title, url.toString()); } @@ -173,9 +157,18 @@ export class State extends EventTarget { return; } + const url = this.#getUrl(false); + this.#pagination.url = url.toString(); + } + + #getUrl(withPageNo: boolean = true): URL { const url = new URL(this.#baseUrl); const parameters: [string, string][] = []; + if (withPageNo && this.#pageNo > 1) { + parameters.push(["pageNo", this.#pageNo.toString()]); + } + for (const parameter of this.#sorting.getQueryParameters()) { parameters.push(parameter); } @@ -189,14 +182,19 @@ export class State extends EventTarget { url.search += new URLSearchParams(parameters).toString(); } - this.#pagination.url = url.toString(); + return url; } #handlePopState(): void { + const url = new URL(window.location.href); + + if (this.#isOnlyHashChange(url)) { + return; + } + let pageNo = 1; - const { searchParams } = new URL(window.location.href); - const value = searchParams.get("pageNo"); + const value = url.searchParams.get("pageNo"); if (value !== null) { pageNo = parseInt(value); if (Number.isNaN(pageNo) || pageNo < 1) { @@ -204,12 +202,25 @@ export class State extends EventTarget { } } - this.#filter.updateFromSearchParams(searchParams); - this.#sorting.updateFromSearchParams(searchParams); + this.#filter.updateFromSearchParams(url.searchParams); + this.#sorting.updateFromSearchParams(url.searchParams); this.#switchPage(pageNo, StateChangeCause.History); } + #updateLastUrl(): void { + if (!this.#baseUrl) { + return; + } + + const url = this.#getUrl(); + this.#lastUrl = url.pathname + url.search; + } + + #isOnlyHashChange(url: URL): boolean { + return url.pathname + url.search === this.#lastUrl; + } + #updateListViewFooter(): void { const hasPagination = this.#pagination.count > 1; this.#listViewFooter.hidden = !hasPagination && !this.#selection.selectionBarVisible(); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/State.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/State.js index 59b2244b45..9b819548b4 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/State.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/State.js @@ -21,6 +21,7 @@ define(["require", "exports", "tslib", "./Filter", "./Selection", "./Sorting"], #selection; #sorting; #gridViewFooter; + #lastUrl; #pageNo; constructor(gridId, table, pageNo, baseUrl, sortField, sortOrder, defaultSortField, defaultSortOrder) { super(); @@ -51,6 +52,7 @@ define(["require", "exports", "tslib", "./Filter", "./Selection", "./Sorting"], this.#handlePopState(); }); } + this.#updateLastUrl(); this.#updatePaginationUrl(); this.#updateGridViewFooter(); } @@ -74,6 +76,7 @@ define(["require", "exports", "tslib", "./Filter", "./Selection", "./Sorting"], this.#pagination.count = count; this.#updatePaginationUrl(); this.#selection.refresh(); + this.#updateLastUrl(); if (cause === 0 /* StateChangeCause.Change */ || cause === 2 /* StateChangeCause.Pagination */) { this.#updateQueryString(); } @@ -88,29 +91,22 @@ define(["require", "exports", "tslib", "./Filter", "./Selection", "./Sorting"], if (!this.#baseUrl) { return; } - const url = new URL(this.#baseUrl); - const parameters = []; - if (this.#pageNo > 1) { - parameters.push(["pageNo", this.#pageNo.toString()]); - } - for (const parameter of this.#sorting.getQueryParameters()) { - parameters.push(parameter); - } - for (const parameter of this.#filter.getQueryParameters()) { - parameters.push(parameter); - } - if (parameters.length > 0) { - url.search += url.search !== "" ? "&" : "?"; - url.search += new URLSearchParams(parameters).toString(); - } + const url = this.#getUrl(); window.history.pushState({}, document.title, url.toString()); } #updatePaginationUrl() { if (!this.#baseUrl) { return; } + const url = this.#getUrl(false); + this.#pagination.url = url.toString(); + } + #getUrl(withPageNo = true) { const url = new URL(this.#baseUrl); const parameters = []; + if (withPageNo && this.#pageNo > 1) { + parameters.push(["pageNo", this.#pageNo.toString()]); + } for (const parameter of this.#sorting.getQueryParameters()) { parameters.push(parameter); } @@ -121,22 +117,35 @@ define(["require", "exports", "tslib", "./Filter", "./Selection", "./Sorting"], url.search += url.search !== "" ? "&" : "?"; url.search += new URLSearchParams(parameters).toString(); } - this.#pagination.url = url.toString(); + return url; } #handlePopState() { + const url = new URL(window.location.href); + if (this.#isOnlyHashChange(url)) { + return; + } let pageNo = 1; - const { searchParams } = new URL(window.location.href); - const value = searchParams.get("pageNo"); + const value = url.searchParams.get("pageNo"); if (value !== null) { pageNo = parseInt(value); if (Number.isNaN(pageNo) || pageNo < 1) { pageNo = 1; } } - this.#filter.updateFromSearchParams(searchParams); - this.#sorting.updateFromSearchParams(searchParams); + this.#filter.updateFromSearchParams(url.searchParams); + this.#sorting.updateFromSearchParams(url.searchParams); this.#switchPage(pageNo, 1 /* StateChangeCause.History */); } + #updateLastUrl() { + if (!this.#baseUrl) { + return; + } + const url = this.#getUrl(); + this.#lastUrl = url.pathname + url.search; + } + #isOnlyHashChange(url) { + return url.pathname + url.search === this.#lastUrl; + } #updateGridViewFooter() { const hasPagination = this.#pagination.count > 1; this.#gridViewFooter.hidden = !hasPagination && !this.#selection.selectionBarVisible(); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/ListView/State.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/ListView/State.js index 9a52ae4444..21ae1bfb6c 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/ListView/State.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/ListView/State.js @@ -21,6 +21,7 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "./F #selection; #sorting; #listViewFooter; + #lastUrl; #pageNo; constructor(viewId, viewElement, pageNo, baseUrl, sortField, sortOrder, defaultSortField, defaultSortOrder) { super(); @@ -59,6 +60,7 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "./F ?.dispatchEvent(new CustomEvent("interaction:invalidate", { bubbles: true })); })); }); + this.#updateLastUrl(); this.#updatePaginationUrl(); this.#updateListViewFooter(); } @@ -82,6 +84,7 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "./F this.#pagination.count = count; this.#updatePaginationUrl(); this.#selection.refresh(); + this.#updateLastUrl(); if (cause === 0 /* StateChangeCause.Change */ || cause === 2 /* StateChangeCause.Pagination */) { this.#updateQueryString(); } @@ -96,29 +99,22 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "./F if (!this.#baseUrl) { return; } - const url = new URL(this.#baseUrl); - const parameters = []; - if (this.#pageNo > 1) { - parameters.push(["pageNo", this.#pageNo.toString()]); - } - for (const parameter of this.#sorting.getQueryParameters()) { - parameters.push(parameter); - } - for (const parameter of this.#filter.getQueryParameters()) { - parameters.push(parameter); - } - if (parameters.length > 0) { - url.search += url.search !== "" ? "&" : "?"; - url.search += new URLSearchParams(parameters).toString(); - } + const url = this.#getUrl(); window.history.pushState({}, document.title, url.toString()); } #updatePaginationUrl() { if (!this.#baseUrl) { return; } + const url = this.#getUrl(false); + this.#pagination.url = url.toString(); + } + #getUrl(withPageNo = true) { const url = new URL(this.#baseUrl); const parameters = []; + if (withPageNo && this.#pageNo > 1) { + parameters.push(["pageNo", this.#pageNo.toString()]); + } for (const parameter of this.#sorting.getQueryParameters()) { parameters.push(parameter); } @@ -129,22 +125,35 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "./F url.search += url.search !== "" ? "&" : "?"; url.search += new URLSearchParams(parameters).toString(); } - this.#pagination.url = url.toString(); + return url; } #handlePopState() { + const url = new URL(window.location.href); + if (this.#isOnlyHashChange(url)) { + return; + } let pageNo = 1; - const { searchParams } = new URL(window.location.href); - const value = searchParams.get("pageNo"); + const value = url.searchParams.get("pageNo"); if (value !== null) { pageNo = parseInt(value); if (Number.isNaN(pageNo) || pageNo < 1) { pageNo = 1; } } - this.#filter.updateFromSearchParams(searchParams); - this.#sorting.updateFromSearchParams(searchParams); + this.#filter.updateFromSearchParams(url.searchParams); + this.#sorting.updateFromSearchParams(url.searchParams); this.#switchPage(pageNo, 1 /* StateChangeCause.History */); } + #updateLastUrl() { + if (!this.#baseUrl) { + return; + } + const url = this.#getUrl(); + this.#lastUrl = url.pathname + url.search; + } + #isOnlyHashChange(url) { + return url.pathname + url.search === this.#lastUrl; + } #updateListViewFooter() { const hasPagination = this.#pagination.count > 1; this.#listViewFooter.hidden = !hasPagination && !this.#selection.selectionBarVisible();