From 00bcf506d098b891a5aedf6cdd84e40ad07245ef Mon Sep 17 00:00:00 2001 From: James Montemagno Date: Wed, 8 Jul 2026 09:31:29 -0700 Subject: [PATCH] Add extension deep linking Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- website/src/scripts/pages/extensions.ts | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/website/src/scripts/pages/extensions.ts b/website/src/scripts/pages/extensions.ts index bce66e1a6..307be21b7 100644 --- a/website/src/scripts/pages/extensions.ts +++ b/website/src/scripts/pages/extensions.ts @@ -52,8 +52,11 @@ let keywordSelect: Choices; let currentFilters = { keywords: [] as string[], }; +const EXTENSION_QUERY_PARAM = "extension"; +let currentOpenExtensionId: string | null = null; let actionHandlersReady = false; let modalReady = false; +let deepLinkObserverReady = false; function normalizeScreenshotEntries(value: unknown): ExtensionScreenshot[] { if (!value) return []; @@ -170,6 +173,60 @@ function setSelectedGalleryImage(url: string, extensionName: string): void { }); } +function syncExtensionQueryParam(extensionId: string | null): void { + currentOpenExtensionId = extensionId; + updateQueryParams({ + [EXTENSION_QUERY_PARAM]: extensionId ?? "", + }); +} + +function findExtensionTrigger(extensionId: string): HTMLElement | undefined { + const card = Array.from( + document.querySelectorAll(".resource-item[data-extension-id]") + ).find((item) => item.dataset.extensionId === extensionId); + return ( + (card?.querySelector(".resource-preview") as HTMLElement | null) || + card || + undefined + ); +} + +function setupDeepLinkObserver(): void { + if (deepLinkObserverReady) return; + + const modal = document.getElementById("file-modal"); + if (!modal) return; + + const observer = new MutationObserver(() => { + if (!modal.classList.contains("hidden") || !currentOpenExtensionId) { + return; + } + + syncExtensionQueryParam(null); + }); + + observer.observe(modal, { + attributes: true, + attributeFilter: ["class"], + }); + + deepLinkObserverReady = true; +} + +function openInitialExtensionFromQuery(): void { + const extensionId = getQueryParam(EXTENSION_QUERY_PARAM); + if (!extensionId) { + return; + } + + if (!extensionById.has(extensionId)) { + syncExtensionQueryParam(null); + return; + } + + openDetailsModal(extensionId, undefined, findExtensionTrigger(extensionId)); +} + function openDetailsModal( extensionId: string, preferredImageUrl?: string, @@ -180,6 +237,8 @@ function openDetailsModal( return; } + syncExtensionQueryParam(extensionId); + const keywordHtml = (item.keywords || []) .map((keyword) => `${escapeHtml(keyword)}`) .join(""); @@ -453,6 +512,7 @@ export async function initExtensionsPage(): Promise { modalReady = true; } + setupDeepLinkObserver(); setupActionHandlers(list as HTMLElement | null); const data = await fetchData("extensions.json"); @@ -523,6 +583,7 @@ export async function initExtensionsPage(): Promise { applySortAndRender(); syncUrlState(); + openInitialExtensionFromQuery(); } // Auto-initialize when DOM is ready