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
@@ -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