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;
|
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.
|
* 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)
|
const packageJson = fs.existsSync(packageJsonPath)
|
||||||
? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))
|
? 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)
|
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))
|
? [...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,
|
installUrl,
|
||||||
sourceUrl: null,
|
sourceUrl: null,
|
||||||
external: false,
|
external: false,
|
||||||
|
author: normalizeAuthor(canvasJson.author),
|
||||||
keywords,
|
keywords,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1116,6 +1140,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
|||||||
installUrl,
|
installUrl,
|
||||||
sourceUrl: sourceUrl || null,
|
sourceUrl: sourceUrl || null,
|
||||||
external: true,
|
external: true,
|
||||||
|
author: normalizeAuthor(ext?.author),
|
||||||
keywords,
|
keywords,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1199,6 +1224,7 @@ function writePerExtensionCanvasManifests(canvasManifestData) {
|
|||||||
name: item.name,
|
name: item.name,
|
||||||
description: item.description || "Canvas extension",
|
description: item.description || "Canvas extension",
|
||||||
version: item.version || "1.0.0",
|
version: item.version || "1.0.0",
|
||||||
|
...(item.author ? { author: item.author } : {}),
|
||||||
keywords: Array.isArray(item.keywords)
|
keywords: Array.isArray(item.keywords)
|
||||||
? [...new Set(item.keywords)].sort((a, b) => a.localeCompare(b))
|
? [...new Set(item.keywords)].sort((a, b) => a.localeCompare(b))
|
||||||
: [],
|
: [],
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Accessibility Kanban",
|
"name": "Accessibility Kanban",
|
||||||
"description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.",
|
"description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Aaron Powell",
|
||||||
|
"url": "https://github.com/aaronpowell"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"accessibility",
|
"accessibility",
|
||||||
"github-issues",
|
"github-issues",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Agent Arcade",
|
"name": "Agent Arcade",
|
||||||
"description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.",
|
"description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Dan Wahlin",
|
||||||
|
"url": "https://github.com/DanWahlin"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"arcade-games",
|
"arcade-games",
|
||||||
"copilot-canvas",
|
"copilot-canvas",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Backlog Swipe Triage",
|
"name": "Backlog Swipe Triage",
|
||||||
"description": "Quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.",
|
"description": "Quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "James Montemagno",
|
||||||
|
"url": "https://github.com/jamesmontemagno"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"agent-assignment",
|
"agent-assignment",
|
||||||
"backlog-triage",
|
"backlog-triage",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Chromium Control Canvas",
|
"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.",
|
"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",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Andrea Griffiths",
|
||||||
|
"url": "https://github.com/AndreaGriffiths11"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"browser-control",
|
"browser-control",
|
||||||
"chromium-browser",
|
"chromium-browser",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Color Orb",
|
"name": "Color Orb",
|
||||||
"description": "A visual orb that users can ask the agent to recolor while showing a live activity log in the canvas.",
|
"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",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Aaron Powell",
|
||||||
|
"url": "https://github.com/aaronpowell"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"agent-actions",
|
"agent-actions",
|
||||||
"color-picker",
|
"color-picker",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Diagram Explorer",
|
"name": "Diagram Explorer",
|
||||||
"description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.",
|
"description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Aaron Powell",
|
||||||
|
"url": "https://github.com/aaronpowell"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"architecture-mapping",
|
"architecture-mapping",
|
||||||
"canvas-navigation",
|
"canvas-navigation",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"id": "coffilot",
|
"id": "coffilot",
|
||||||
"name": "Coffilot",
|
"name": "Coffilot",
|
||||||
"description": "Java-focused Copilot canvas extension from jdubois.",
|
"description": "Java-focused Copilot canvas extension from jdubois.",
|
||||||
|
"author": { "name": "Julien Dubois", "url": "https://github.com/jdubois" },
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"java",
|
"java",
|
||||||
"canvas",
|
"canvas",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Feedback Themes",
|
"name": "Feedback Themes",
|
||||||
"description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.",
|
"description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Aaron Powell",
|
||||||
|
"url": "https://github.com/aaronpowell"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"customer-feedback",
|
"customer-feedback",
|
||||||
"impact-prioritization",
|
"impact-prioritization",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Gesture PR Review",
|
"name": "Gesture PR Review",
|
||||||
"description": "Review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.",
|
"description": "Review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Aaron Powell",
|
||||||
|
"url": "https://github.com/aaronpowell"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"camera-input",
|
"camera-input",
|
||||||
"gesture-control",
|
"gesture-control",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Release Notes Showcase",
|
"name": "Release Notes Showcase",
|
||||||
"description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.",
|
"description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "James Montemagno",
|
||||||
|
"url": "https://github.com/jamesmontemagno"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"changelog",
|
"changelog",
|
||||||
"contributor-callouts",
|
"contributor-callouts",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"name": "Where Was I?",
|
"name": "Where Was I?",
|
||||||
"description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.",
|
"description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Aaron Powell",
|
||||||
|
"url": "https://github.com/aaronpowell"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"branch-state",
|
"branch-state",
|
||||||
"developer-context",
|
"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";
|
import { renderEmptyStateHtml, renderSharedCardHtml } from "./card-render";
|
||||||
|
|
||||||
export interface RenderableExtension {
|
export interface RenderableExtension {
|
||||||
@@ -34,6 +40,7 @@ export interface RenderableExtension {
|
|||||||
installUrl?: string | null;
|
installUrl?: string | null;
|
||||||
sourceUrl?: string | null;
|
sourceUrl?: string | null;
|
||||||
external?: boolean;
|
external?: boolean;
|
||||||
|
author?: { name: string; url?: string } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExtensionSortOption = "title" | "lastUpdated";
|
export type ExtensionSortOption = "title" | "lastUpdated";
|
||||||
@@ -92,8 +99,27 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string {
|
|||||||
</div>
|
</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 = `
|
const metaHtml = `
|
||||||
${item.external ? '<span class="resource-tag">External</span>' : ""}
|
${item.external ? '<span class="resource-tag">External</span>' : ""}
|
||||||
|
${authorHtml}
|
||||||
${getLastUpdatedHtml(item.lastUpdated)}
|
${getLastUpdatedHtml(item.lastUpdated)}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import {
|
|||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
fetchData,
|
fetchData,
|
||||||
formatRelativeTime,
|
formatRelativeTime,
|
||||||
|
getGitHubHandle,
|
||||||
getGitHubUrl,
|
getGitHubUrl,
|
||||||
getQueryParam,
|
getQueryParam,
|
||||||
getQueryParamValues,
|
getQueryParamValues,
|
||||||
|
sanitizeUrl,
|
||||||
showToast,
|
showToast,
|
||||||
updateQueryParams,
|
updateQueryParams,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
@@ -178,6 +180,24 @@ function openDetailsModal(
|
|||||||
if (item.external) {
|
if (item.external) {
|
||||||
metaParts.push('<span class="resource-tag">External</span>');
|
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) {
|
if (item.lastUpdated) {
|
||||||
metaParts.push(
|
metaParts.push(
|
||||||
`<span class="last-updated">Updated ${escapeHtml(
|
`<span class="last-updated">Updated ${escapeHtml(
|
||||||
|
|||||||
@@ -389,6 +389,31 @@ export function sanitizeUrl(url: string | null | undefined): string {
|
|||||||
return "#";
|
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
|
* Truncate text with ellipsis
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user