mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-25 08:57:43 +00:00
chore: publish from main
This commit is contained in:
@@ -101,6 +101,25 @@ function normalizeText(value, fallback = "") {
|
||||
return typeof value === "string" ? value.trim() : fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an author value (npm string form or { name, url } object) to
|
||||
* { name, url? } | null. Returns null when no usable name is present.
|
||||
*/
|
||||
function normalizeAuthor(value) {
|
||||
if (!value) return null;
|
||||
if (typeof value === "string") {
|
||||
const name = value.trim();
|
||||
return name ? { name } : null;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const name = normalizeText(value.name);
|
||||
if (!name) return null;
|
||||
const url = normalizeText(value.url);
|
||||
return url ? { name, url } : { name };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the latest git-modified date for any file under a directory.
|
||||
*/
|
||||
@@ -1002,6 +1021,10 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
const packageJson = fs.existsSync(packageJsonPath)
|
||||
? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))
|
||||
: {};
|
||||
const canvasJsonPath = path.join(extensionDir, "canvas.json");
|
||||
const canvasJson = fs.existsSync(canvasJsonPath)
|
||||
? JSON.parse(fs.readFileSync(canvasJsonPath, "utf-8"))
|
||||
: {};
|
||||
const keywords = Array.isArray(packageJson.keywords)
|
||||
? [...new Set(packageJson.keywords.filter((keyword) => typeof keyword === "string").map((keyword) => keyword.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b))
|
||||
: [];
|
||||
@@ -1044,6 +1067,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
installUrl,
|
||||
sourceUrl: null,
|
||||
external: false,
|
||||
author: normalizeAuthor(canvasJson.author),
|
||||
keywords,
|
||||
});
|
||||
}
|
||||
@@ -1116,6 +1140,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
installUrl,
|
||||
sourceUrl: sourceUrl || null,
|
||||
external: true,
|
||||
author: normalizeAuthor(ext?.author),
|
||||
keywords,
|
||||
});
|
||||
}
|
||||
@@ -1199,6 +1224,7 @@ function writePerExtensionCanvasManifests(canvasManifestData) {
|
||||
name: item.name,
|
||||
description: item.description || "Canvas extension",
|
||||
version: item.version || "1.0.0",
|
||||
...(item.author ? { author: item.author } : {}),
|
||||
keywords: Array.isArray(item.keywords)
|
||||
? [...new Set(item.keywords)].sort((a, b) => a.localeCompare(b))
|
||||
: [],
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Accessibility Kanban",
|
||||
"description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Aaron Powell",
|
||||
"url": "https://github.com/aaronpowell"
|
||||
},
|
||||
"keywords": [
|
||||
"accessibility",
|
||||
"github-issues",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Agent Arcade",
|
||||
"description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Dan Wahlin",
|
||||
"url": "https://github.com/DanWahlin"
|
||||
},
|
||||
"keywords": [
|
||||
"arcade-games",
|
||||
"copilot-canvas",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Backlog Swipe Triage",
|
||||
"description": "Quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "James Montemagno",
|
||||
"url": "https://github.com/jamesmontemagno"
|
||||
},
|
||||
"keywords": [
|
||||
"agent-assignment",
|
||||
"backlog-triage",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Chromium Control Canvas",
|
||||
"description": "Opens a real Chromium window you can navigate and interact with from a Copilot canvas control panel and agent actions.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Andrea Griffiths",
|
||||
"url": "https://github.com/AndreaGriffiths11"
|
||||
},
|
||||
"keywords": [
|
||||
"browser-control",
|
||||
"chromium-browser",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Color Orb",
|
||||
"description": "A visual orb that users can ask the agent to recolor while showing a live activity log in the canvas.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Aaron Powell",
|
||||
"url": "https://github.com/aaronpowell"
|
||||
},
|
||||
"keywords": [
|
||||
"agent-actions",
|
||||
"color-picker",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Diagram Explorer",
|
||||
"description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Aaron Powell",
|
||||
"url": "https://github.com/aaronpowell"
|
||||
},
|
||||
"keywords": [
|
||||
"architecture-mapping",
|
||||
"canvas-navigation",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"id": "coffilot",
|
||||
"name": "Coffilot",
|
||||
"description": "Java-focused Copilot canvas extension from jdubois.",
|
||||
"author": { "name": "Julien Dubois", "url": "https://github.com/jdubois" },
|
||||
"keywords": [
|
||||
"java",
|
||||
"canvas",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Feedback Themes",
|
||||
"description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Aaron Powell",
|
||||
"url": "https://github.com/aaronpowell"
|
||||
},
|
||||
"keywords": [
|
||||
"customer-feedback",
|
||||
"impact-prioritization",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Gesture PR Review",
|
||||
"description": "Review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Aaron Powell",
|
||||
"url": "https://github.com/aaronpowell"
|
||||
},
|
||||
"keywords": [
|
||||
"camera-input",
|
||||
"gesture-control",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Release Notes Showcase",
|
||||
"description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "James Montemagno",
|
||||
"url": "https://github.com/jamesmontemagno"
|
||||
},
|
||||
"keywords": [
|
||||
"changelog",
|
||||
"contributor-callouts",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"name": "Where Was I?",
|
||||
"description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Aaron Powell",
|
||||
"url": "https://github.com/aaronpowell"
|
||||
},
|
||||
"keywords": [
|
||||
"branch-state",
|
||||
"developer-context",
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { escapeHtml, getGitHubUrl, getLastUpdatedHtml } from "../utils";
|
||||
import {
|
||||
escapeHtml,
|
||||
getGitHubHandle,
|
||||
getGitHubUrl,
|
||||
getLastUpdatedHtml,
|
||||
sanitizeUrl,
|
||||
} from "../utils";
|
||||
import { renderEmptyStateHtml, renderSharedCardHtml } from "./card-render";
|
||||
|
||||
export interface RenderableExtension {
|
||||
@@ -34,6 +40,7 @@ export interface RenderableExtension {
|
||||
installUrl?: string | null;
|
||||
sourceUrl?: string | null;
|
||||
external?: boolean;
|
||||
author?: { name: string; url?: string } | null;
|
||||
}
|
||||
|
||||
export type ExtensionSortOption = "title" | "lastUpdated";
|
||||
@@ -92,8 +99,27 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string {
|
||||
</div>
|
||||
`;
|
||||
|
||||
const authorName = item.author?.name;
|
||||
const authorUrl = item.author?.url;
|
||||
const authorHandle =
|
||||
authorName && authorUrl
|
||||
? getGitHubHandle(authorUrl, authorName)
|
||||
: authorName || "";
|
||||
const authorHtml = authorName
|
||||
? `<span class="resource-tag resource-author">by ${
|
||||
authorUrl
|
||||
? `<a href="${escapeHtml(
|
||||
sanitizeUrl(authorUrl)
|
||||
)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(
|
||||
authorName
|
||||
)}">${escapeHtml(authorHandle)}</a>`
|
||||
: escapeHtml(authorName)
|
||||
}</span>`
|
||||
: "";
|
||||
|
||||
const metaHtml = `
|
||||
${item.external ? '<span class="resource-tag">External</span>' : ""}
|
||||
${authorHtml}
|
||||
${getLastUpdatedHtml(item.lastUpdated)}
|
||||
`;
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ import {
|
||||
copyToClipboard,
|
||||
fetchData,
|
||||
formatRelativeTime,
|
||||
getGitHubHandle,
|
||||
getGitHubUrl,
|
||||
getQueryParam,
|
||||
getQueryParamValues,
|
||||
sanitizeUrl,
|
||||
showToast,
|
||||
updateQueryParams,
|
||||
} from "../utils";
|
||||
@@ -178,6 +180,24 @@ function openDetailsModal(
|
||||
if (item.external) {
|
||||
metaParts.push('<span class="resource-tag">External</span>');
|
||||
}
|
||||
if (item.author?.name) {
|
||||
const authorName = item.author.name;
|
||||
const authorUrl = item.author.url;
|
||||
const authorHandle = authorUrl
|
||||
? getGitHubHandle(authorUrl, authorName)
|
||||
: authorName;
|
||||
metaParts.push(
|
||||
authorUrl
|
||||
? `<span class="resource-author">by <a href="${escapeHtml(
|
||||
sanitizeUrl(authorUrl)
|
||||
)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(
|
||||
authorName
|
||||
)}">${escapeHtml(authorHandle)}</a></span>`
|
||||
: `<span class="resource-author">by ${escapeHtml(
|
||||
authorName
|
||||
)}</span>`
|
||||
);
|
||||
}
|
||||
if (item.lastUpdated) {
|
||||
metaParts.push(
|
||||
`<span class="last-updated">Updated ${escapeHtml(
|
||||
|
||||
@@ -389,6 +389,31 @@ export function sanitizeUrl(url: string | null | undefined): string {
|
||||
return "#";
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a GitHub @handle from a profile URL
|
||||
* (e.g. "https://github.com/aaronpowell" -> "@aaronpowell").
|
||||
* Falls back to the provided value when the URL is not a github.com profile.
|
||||
*/
|
||||
export function getGitHubHandle(
|
||||
url: string | null | undefined,
|
||||
fallback = ""
|
||||
): string {
|
||||
if (!url) return fallback;
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
const host = parsed.hostname.replace(/^www\./, "");
|
||||
if (host === "github.com") {
|
||||
const segments = parsed.pathname.split("/").filter(Boolean);
|
||||
if (segments.length === 1) {
|
||||
return `@${segments[0]}`;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Invalid URL
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate text with ellipsis
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user