mirror of
https://github.com/github/awesome-copilot.git
synced 2026-03-12 04:05:12 +00:00
feat(website): add Open Graph and Twitter Card meta tags for social sharing
- Add og:type, og:url, og:title, og:description, og:image, og:site_name meta tags - Add twitter:card, twitter:title, twitter:description, twitter:image meta tags - Add canonical URL link element - Use social-image.png for social preview image - Update document.title dynamically when modal opens/closes - Resolve resource titles from JSON data files instead of raw filenames - Handle skill/hook folder path mismatches for title lookup - Change title separator from '-' to '|' for consistency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
BIN
website/public/images/social-image.png
Normal file
BIN
website/public/images/social-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 425 KiB |
@@ -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 {
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{title} - Awesome GitHub Copilot</title>
|
||||
<title>{title} | Awesome GitHub Copilot</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={canonicalUrl.toString()} />
|
||||
<meta property="og:title" content={`${title} | Awesome GitHub Copilot`} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={socialImageUrl.toString()} />
|
||||
<meta property="og:site_name" content="Awesome GitHub Copilot" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={`${title} | Awesome GitHub Copilot`} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={socialImageUrl.toString()} />
|
||||
|
||||
<link rel="stylesheet" href={`${base}styles/global.css`} />
|
||||
<link
|
||||
rel="icon"
|
||||
|
||||
@@ -20,6 +20,61 @@ let currentFilePath: string | null = null;
|
||||
let currentFileContent: string | null = null;
|
||||
let currentFileType: string | null = null;
|
||||
let triggerElement: HTMLElement | null = null;
|
||||
let originalDocumentTitle: string | null = null;
|
||||
|
||||
// Resource data cache for title lookups
|
||||
interface ResourceItem {
|
||||
title: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
interface ResourceData {
|
||||
items: ResourceItem[];
|
||||
}
|
||||
|
||||
const resourceDataCache: Record<string, ResourceData | null> = {};
|
||||
|
||||
const RESOURCE_TYPE_TO_JSON: Record<string, string> = {
|
||||
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<string> {
|
||||
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<ResourceData>(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 &&
|
||||
|
||||
Reference in New Issue
Block a user