diff --git a/website/public/images/social-image.png b/website/public/images/social-image.png new file mode 100644 index 000000000..76abe3fe9 Binary files /dev/null and b/website/public/images/social-image.png differ diff --git a/website/src/layouts/BaseLayout.astro b/website/src/layouts/BaseLayout.astro index 5efa9caef..35fc684d7 100644 --- a/website/src/layouts/BaseLayout.astro +++ b/website/src/layouts/BaseLayout.astro @@ -13,6 +13,8 @@ const { activeNav = "", } = Astro.props; const base = import.meta.env.BASE_URL; +const canonicalUrl = new URL(Astro.url.pathname, Astro.site); +const socialImageUrl = new URL(`${base}images/social-image.png`, Astro.site); // Get git commit SHA and build date at build time let commitSha = "unknown"; @@ -35,8 +37,24 @@ try { - {title} - Awesome GitHub Copilot + {title} | Awesome GitHub Copilot + + + + + + + + + + + + + + + + = {}; + +const RESOURCE_TYPE_TO_JSON: Record = { + agent: "agents.json", + instruction: "instructions.json", + skill: "skills.json", + hook: "hooks.json", + workflow: "workflows.json", + plugin: "plugins.json", +}; + +/** + * Look up the display title for a resource from its JSON data file + */ +async function resolveResourceTitle( + filePath: string, + type: string +): Promise { + const fallback = filePath.split("/").pop() || filePath; + const jsonFile = RESOURCE_TYPE_TO_JSON[type]; + if (!jsonFile) return fallback; + + if (!(jsonFile in resourceDataCache)) { + resourceDataCache[jsonFile] = await fetchData(jsonFile); + } + + const data = resourceDataCache[jsonFile]; + if (!data) return fallback; + + // Try exact path match first + const item = data.items.find((i) => i.path === filePath); + if (item) return item.title; + + // For skills/hooks, the modal receives the file path (e.g. skills/foo/SKILL.md) + // but JSON stores the folder path (e.g. skills/foo) + const parentPath = filePath.substring(0, filePath.lastIndexOf("/")); + if (parentPath) { + const parentItem = data.items.find((i) => i.path === parentPath); + if (parentItem) return parentItem.title; + } + + return fallback; +} // Plugin data cache interface PluginItem { @@ -329,9 +384,24 @@ export async function openFileModal( } // Show modal with loading state - title.textContent = filePath.split("/").pop() || filePath; + const fallbackName = filePath.split("/").pop() || filePath; + title.textContent = fallbackName; modal.classList.remove("hidden"); + // Update document title to reflect the open file + if (!originalDocumentTitle) { + originalDocumentTitle = document.title; + } + document.title = `${fallbackName} | Awesome GitHub Copilot`; + + // Resolve the proper title from JSON data asynchronously + resolveResourceTitle(filePath, type).then((resolvedTitle) => { + if (currentFilePath === filePath) { + title.textContent = resolvedTitle; + document.title = `${resolvedTitle} | Awesome GitHub Copilot`; + } + }); + // Set focus to close button for accessibility setTimeout(() => { closeBtn?.focus(); @@ -447,6 +517,7 @@ async function openPluginModal( // Update title title.textContent = plugin.name; + document.title = `${plugin.name} | Awesome GitHub Copilot`; // Render plugin view modalContent.innerHTML = ` @@ -531,6 +602,12 @@ export function closeModal(updateUrl = true): void { updateHash(null); } + // Restore original document title + if (originalDocumentTitle) { + document.title = originalDocumentTitle; + originalDocumentTitle = null; + } + // Return focus to trigger element if ( triggerElement &&