mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-17 21:21:20 +00:00
chore: publish from staged
This commit is contained in:
@@ -20,9 +20,13 @@ const initialItems = sortExtensions(extensionsData.items, 'title');
|
||||
<div class="listing-toolbar-row">
|
||||
<div class="results-count" id="results-count" aria-live="polite">{initialItems.length} extensions</div>
|
||||
<details class="listing-controls">
|
||||
<summary class="listing-controls-trigger btn btn-secondary btn-small">Sort</summary>
|
||||
<summary class="listing-controls-trigger btn btn-secondary btn-small">Sort & Filter</summary>
|
||||
<div class="listing-controls-panel">
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<label for="filter-keyword">Keyword:</label>
|
||||
<select id="filter-keyword" multiple aria-label="Filter by keyword"></select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="sort-select">Sort:</label>
|
||||
<select id="sort-select" aria-label="Sort extensions">
|
||||
@@ -30,6 +34,7 @@ const initialItems = sortExtensions(extensionsData.items, 'title');
|
||||
<option value="lastUpdated">Recently Updated</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -2,11 +2,26 @@ import { escapeHtml, getGitHubUrl, getLastUpdatedHtml } from "../utils";
|
||||
|
||||
export interface RenderableExtension {
|
||||
id: string;
|
||||
canvasId?: string;
|
||||
extensionId?: string;
|
||||
extensionName?: string;
|
||||
name: string;
|
||||
path?: string | null;
|
||||
ref?: string | null;
|
||||
version?: string | null;
|
||||
description?: string;
|
||||
lastUpdated?: string | null;
|
||||
keywords?: string[];
|
||||
screenshots?: {
|
||||
icon?: {
|
||||
path?: string | null;
|
||||
type?: string | null;
|
||||
} | null;
|
||||
gallery?: {
|
||||
path?: string | null;
|
||||
type?: string | null;
|
||||
} | null;
|
||||
} | null;
|
||||
imageUrl?: string | null;
|
||||
assetPath?: string | null;
|
||||
installUrl?: string | null;
|
||||
@@ -69,6 +84,18 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string {
|
||||
<div class="resource-description">${escapeHtml(
|
||||
item.description || "Canvas extension"
|
||||
)}</div>
|
||||
<div class="resource-keywords">
|
||||
${
|
||||
item.keywords && item.keywords.length > 0
|
||||
? item.keywords
|
||||
.map(
|
||||
(kw) =>
|
||||
`<span class="keyword-tag">${escapeHtml(kw)}</span>`
|
||||
)
|
||||
.join("")
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="resource-meta">
|
||||
${
|
||||
item.external
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
/**
|
||||
* Canvas extensions page functionality
|
||||
*/
|
||||
import {
|
||||
createChoices,
|
||||
getChoicesValues,
|
||||
setChoicesValues,
|
||||
type Choices,
|
||||
} from "../choices";
|
||||
import {
|
||||
copyToClipboard,
|
||||
fetchData,
|
||||
getQueryParam,
|
||||
getQueryParamValues,
|
||||
showToast,
|
||||
updateQueryParams,
|
||||
} from "../utils";
|
||||
@@ -17,14 +24,22 @@ import {
|
||||
|
||||
interface Extension extends RenderableExtension {
|
||||
lastUpdated?: string | null;
|
||||
keywords?: string[];
|
||||
}
|
||||
|
||||
interface ExtensionsData {
|
||||
items: Extension[];
|
||||
filters?: {
|
||||
keywords?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
let allItems: Extension[] = [];
|
||||
let currentSort: ExtensionSortOption = "title";
|
||||
let keywordSelect: Choices;
|
||||
let currentFilters = {
|
||||
keywords: [] as string[],
|
||||
};
|
||||
let actionHandlersReady = false;
|
||||
|
||||
function openPreviewModal(url: string, alt: string): void {
|
||||
@@ -51,13 +66,33 @@ function closePreviewModal(): void {
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
|
||||
function sortItems(items: Extension[]): Extension[] {
|
||||
return sortExtensions(items, currentSort);
|
||||
}
|
||||
|
||||
function getCountText(resultsCount: number): string {
|
||||
if (currentFilters.keywords.length === 0) {
|
||||
return `${resultsCount} extension${resultsCount === 1 ? "" : "s"}`;
|
||||
}
|
||||
|
||||
return `${resultsCount} of ${allItems.length} extensions (filtered by ${currentFilters.keywords.length} keyword${currentFilters.keywords.length === 1 ? "" : "s"})`;
|
||||
}
|
||||
|
||||
function applySortAndRender(): void {
|
||||
const countEl = document.getElementById("results-count");
|
||||
const results = sortExtensions(allItems, currentSort);
|
||||
let results = [...allItems];
|
||||
|
||||
if (currentFilters.keywords.length > 0) {
|
||||
results = results.filter((item) =>
|
||||
item.keywords?.some((keyword) => currentFilters.keywords.includes(keyword))
|
||||
);
|
||||
}
|
||||
|
||||
results = sortItems(results);
|
||||
|
||||
renderItems(results);
|
||||
if (countEl) {
|
||||
countEl.textContent = `${results.length} extension${results.length === 1 ? "" : "s"}`;
|
||||
countEl.textContent = getCountText(results.length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,12 +167,15 @@ function setupActionHandlers(list: HTMLElement | null): void {
|
||||
|
||||
function syncUrlState(): void {
|
||||
updateQueryParams({
|
||||
q: "",
|
||||
keyword: currentFilters.keywords,
|
||||
sort: currentSort === "title" ? "" : currentSort,
|
||||
});
|
||||
}
|
||||
|
||||
export async function initExtensionsPage(): Promise<void> {
|
||||
const list = document.getElementById("resource-list");
|
||||
const clearFiltersBtn = document.getElementById("clear-filters");
|
||||
const sortSelect = document.getElementById(
|
||||
"sort-select"
|
||||
) as HTMLSelectElement;
|
||||
@@ -154,19 +192,63 @@ export async function initExtensionsPage(): Promise<void> {
|
||||
|
||||
allItems = data.items;
|
||||
|
||||
const availableKeywords = (
|
||||
data.filters?.keywords ||
|
||||
Array.from(
|
||||
new Set(
|
||||
data.items.flatMap((item) =>
|
||||
Array.isArray(item.keywords) ? item.keywords : []
|
||||
)
|
||||
)
|
||||
)
|
||||
).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
keywordSelect = createChoices("#filter-keyword", {
|
||||
placeholderValue: "All Keywords",
|
||||
});
|
||||
keywordSelect.setChoices(
|
||||
availableKeywords.map((keyword) => ({ value: keyword, label: keyword })),
|
||||
"value",
|
||||
"label",
|
||||
true
|
||||
);
|
||||
|
||||
const initialKeywords = getQueryParamValues("keyword").filter((keyword) =>
|
||||
availableKeywords.includes(keyword)
|
||||
);
|
||||
const initialSort = getQueryParam("sort");
|
||||
if (initialKeywords.length > 0) {
|
||||
currentFilters.keywords = initialKeywords;
|
||||
setChoicesValues(keywordSelect, initialKeywords);
|
||||
}
|
||||
if (initialSort === "lastUpdated") {
|
||||
currentSort = initialSort;
|
||||
if (sortSelect) sortSelect.value = initialSort;
|
||||
}
|
||||
|
||||
document.getElementById("filter-keyword")?.addEventListener("change", () => {
|
||||
currentFilters.keywords = getChoicesValues(keywordSelect);
|
||||
applySortAndRender();
|
||||
syncUrlState();
|
||||
});
|
||||
|
||||
sortSelect?.addEventListener("change", () => {
|
||||
currentSort = sortSelect.value as ExtensionSortOption;
|
||||
applySortAndRender();
|
||||
syncUrlState();
|
||||
});
|
||||
|
||||
clearFiltersBtn?.addEventListener("click", () => {
|
||||
currentFilters = { keywords: [] };
|
||||
currentSort = "title";
|
||||
keywordSelect.removeActiveItems();
|
||||
if (sortSelect) sortSelect.value = "title";
|
||||
applySortAndRender();
|
||||
syncUrlState();
|
||||
});
|
||||
|
||||
applySortAndRender();
|
||||
syncUrlState();
|
||||
}
|
||||
|
||||
// Auto-initialize when DOM is ready
|
||||
|
||||
@@ -1971,6 +1971,23 @@ body:has(#main-content) {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.resource-keywords {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.keyword-tag {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
padding: 3px 8px;
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-radius: 10px;
|
||||
color: var(--color-text-muted);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.resource-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
Reference in New Issue
Block a user