chore: publish from staged

This commit is contained in:
github-actions[bot]
2026-06-17 05:28:37 +00:00
parent 97205f47ef
commit 2512d7d728
29 changed files with 923 additions and 89 deletions
+6 -1
View File
@@ -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 &amp; 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
+84 -2
View File
@@ -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
+17
View File
@@ -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;