From 6a8900fac267b7d0dffa8b1d231b1c907ed6126d Mon Sep 17 00:00:00 2001 From: Aotumuri Date: Sun, 14 Jun 2026 19:58:09 +0000 Subject: [PATCH] feat: Support direct clan detail links (#3928) ## Description: Add support for opening clan details directly with `clan=` ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: aotumuri --- src/client/ClanModal.ts | 10 +++++++++- src/client/ModalRouter.ts | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/client/ClanModal.ts b/src/client/ClanModal.ts index aea8b16c5..6ab325acc 100644 --- a/src/client/ClanModal.ts +++ b/src/client/ClanModal.ts @@ -18,6 +18,7 @@ import "./components/clan/ClanTransferView"; import "./components/ConfirmDialog"; import "./components/CopyButton"; import { modalHeader } from "./components/ui/ModalHeader"; +import { modalRouter } from "./ModalRouter"; import { translateText } from "./Utils"; type View = @@ -195,6 +196,7 @@ export class ClanModal extends BaseModal { this.selectedClanTag = ""; this.myRole = null; this.detailCache = null; + modalRouter.syncArgs("clan", { clan: null, tag: null }); this.gameHistoryCache = null; this.setActiveTab(this.previousListTab); }, @@ -204,7 +206,12 @@ export class ClanModal extends BaseModal { } protected onOpen(args?: Record): void { - const targetTag = typeof args?.tag === "string" ? args.tag : ""; + const targetTag = + typeof args?.clan === "string" + ? args.clan.trim() + : typeof args?.tag === "string" + ? args.tag.trim() + : ""; if (targetTag) { this.openDetail(targetTag.toUpperCase()); } @@ -489,6 +496,7 @@ export class ClanModal extends BaseModal { } this.selectedClanTag = tag; this.view = "detail"; + modalRouter.syncArgs("clan", { clan: tag, tag: null }); // modalConfig() returns detail tabs; setActiveTab anchors activeTab to // "overview" and syncs the URL router (routerName = "clan"). this.setActiveTab("overview"); diff --git a/src/client/ModalRouter.ts b/src/client/ModalRouter.ts index a2224f86e..016224057 100644 --- a/src/client/ModalRouter.ts +++ b/src/client/ModalRouter.ts @@ -123,6 +123,24 @@ class ModalRouter { this.replaceHash("#" + params.toString()); } + /** Called when a router-managed modal changes non-tab route state. */ + syncArgs(name: string, args: Record): void { + if (this.routingFromUrl) return; + if (this.currentName !== name) return; + const params = this.currentHashParams(); + params.set("modal", name); + for (const [key, value] of Object.entries(args)) { + if (key === "modal") continue; + if (value === undefined || value === null || value === "") { + params.delete(key); + continue; + } + if (typeof value === "object") continue; + params.set(key, String(value)); + } + this.replaceHash("#" + params.toString()); + } + /** True if the current hash is `#modal=...`. */ isHashRouted(): boolean { const hash = window.location.hash; @@ -142,7 +160,7 @@ class ModalRouter { if (args) { for (const [key, value] of Object.entries(args)) { if (key === "modal") continue; - if (value === undefined || value === null) continue; + if (value === undefined || value === null || value === "") continue; if (typeof value === "object") continue; params.set(key, String(value)); }