From 99dda4f7e8fada2468c42c95c68940d5a5b879d0 Mon Sep 17 00:00:00 2001 From: David Brett Date: Thu, 2 Apr 2026 10:15:58 +0200 Subject: [PATCH 1/7] feat: add sorting functionality to plugins page, by name --- src/pages/plugins/index.astro | 79 +++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/src/pages/plugins/index.astro b/src/pages/plugins/index.astro index 405843b..9611aac 100644 --- a/src/pages/plugins/index.astro +++ b/src/pages/plugins/index.astro @@ -45,6 +45,13 @@ const hasFeaturedPlugins = featured.length > 0; {TAGS.map(v => )} + + + +
{plugins.map(v => )} @@ -60,7 +67,7 @@ const hasFeaturedPlugins = featured.length > 0; .controls { display: grid; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 24px; position: sticky; @@ -102,36 +109,59 @@ import {TAGS} from "@/constants"; const searchInput = document.querySelector("#search") as HTMLInputElement; const tagsSelect = document.querySelector("#tags") as HTMLSelectElement; +const sortSelect = document.querySelector("#sort") as HTMLSelectElement; const notFound = document.querySelector("#not-found") as HTMLElement; +const store = document.querySelector("#store") as HTMLElement; const plugins = document.querySelectorAll("#store > .plugin") as NodeListOf; +const defaultSortValue = "name-asc"; +const allowedSortValues = ["name-asc", "name-desc"]; -searchInput.addEventListener("input", filterPlugins); -tagsSelect.addEventListener("change", filterPlugins); +searchInput.addEventListener("input", onFilterChange); +tagsSelect.addEventListener("change", onFilterChange); +sortSelect.addEventListener("change", onSortChange); const url = new URL(window.location.href); const searchParam = url.searchParams.get("search") ?? ""; let tagsParam: any = url.searchParams.get("tags") ?? ""; +let sortParam = url.searchParams.get("sort") ?? defaultSortValue; if (!TAGS.includes(tagsParam)) { tagsParam = ""; } +if (!allowedSortValues.includes(sortParam)) { + sortParam = defaultSortValue; +} + searchInput.value = searchParam; tagsSelect.value = tagsParam; +sortSelect.value = sortParam; filterPlugins(); +applySort(); +updateUrlParams(); -function filterPlugins(): void { - let foundAtLeastOnePlugin = false; +function onFilterChange(): void { + filterPlugins(); + applySort(); + updateUrlParams(); +} - const searchValue = searchInput.value.toLowerCase(); +function onSortChange(): void { + applySort(); + updateUrlParams(); +} + +function updateUrlParams(): void { + const searchValue = searchInput.value; const tagsValue = tagsSelect.value; + const sortValue = sortSelect.value; const url = new URL(window.location.href); if (searchValue.length > 0) { - url.searchParams.set("search", searchInput.value); + url.searchParams.set("search", searchValue); } else { url.searchParams.delete("search"); } @@ -142,7 +172,20 @@ function filterPlugins(): void { url.searchParams.delete("tags"); } + if (allowedSortValues.includes(sortValue)) { + url.searchParams.set("sort", sortValue); + } else { + url.searchParams.delete("sort"); + } + history.replaceState(null, "", url); +} + +function filterPlugins(): void { + let foundAtLeastOnePlugin = false; + + const searchValue = searchInput.value.toLowerCase(); + const tagsValue = tagsSelect.value; for (const plugin of plugins) { const title = plugin.querySelector(".name")!.textContent!.toLowerCase(); @@ -169,4 +212,26 @@ function filterPlugins(): void { notFound.style.display = "block"; } } + +function applySort(): void { + const sortValue = sortSelect.value; + + if (!allowedSortValues.includes(sortValue)) { + return; + } + + const sortedPlugins = Array.from(plugins).sort((a, b) => { + const titleA = a.querySelector(".name")!.textContent!.trim().toLowerCase(); + const titleB = b.querySelector(".name")!.textContent!.trim().toLowerCase(); + + if (sortValue === "name-desc") { + return titleB.localeCompare(titleA); + } + + return titleA.localeCompare(titleB); + }); + + // Re-appending existing plugin nodes moves them into sorted order (no duplicates) + store.append(...sortedPlugins); +} From a65e265326a9e7697036cc0da54f697648facee6 Mon Sep 17 00:00:00 2001 From: David Brett Date: Thu, 2 Apr 2026 12:02:32 +0200 Subject: [PATCH 2/7] feat: add support for sorting plugins by first and latest release --- src/pages/plugins/index.astro | 53 +++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/src/pages/plugins/index.astro b/src/pages/plugins/index.astro index 9611aac..70bba0a 100644 --- a/src/pages/plugins/index.astro +++ b/src/pages/plugins/index.astro @@ -50,11 +50,17 @@ const hasFeaturedPlugins = featured.length > 0;
- {plugins.map(v => )} + {plugins.map(v => )}
@@ -115,7 +121,12 @@ const store = document.querySelector("#store") as HTMLElement; const plugins = document.querySelectorAll("#store > .plugin") as NodeListOf; const defaultSortValue = "name-asc"; -const allowedSortValues = ["name-asc", "name-desc"]; +const allowedSortValues = [ + "name-asc", + "name-desc", + "latest-release-desc", + "first-release-desc", +]; searchInput.addEventListener("input", onFilterChange); tagsSelect.addEventListener("change", onFilterChange); @@ -213,6 +224,12 @@ function filterPlugins(): void { } } +function getDateTimestamp(plugin: HTMLElement, attributeName: string): number { + const dateValue = plugin.getAttribute(attributeName) ?? ""; + const timestamp = Date.parse(dateValue); + return Number.isNaN(timestamp) ? 0 : timestamp; +} + function applySort(): void { const sortValue = sortSelect.value; @@ -221,14 +238,32 @@ function applySort(): void { } const sortedPlugins = Array.from(plugins).sort((a, b) => { - const titleA = a.querySelector(".name")!.textContent!.trim().toLowerCase(); - const titleB = b.querySelector(".name")!.textContent!.trim().toLowerCase(); - - if (sortValue === "name-desc") { - return titleB.localeCompare(titleA); + switch (sortValue) { + case "latest-release-desc": { + const latestA = getDateTimestamp(a, "data-latest-release-date"); + const latestB = getDateTimestamp(b, "data-latest-release-date"); + return latestB - latestA; + } + + case "first-release-desc": { + const firstA = getDateTimestamp(a, "data-first-release-date"); + const firstB = getDateTimestamp(b, "data-first-release-date"); + return firstB - firstA; + } + + case "name-desc": { + const titleA = a.querySelector(".name")!.textContent!.trim().toLowerCase(); + const titleB = b.querySelector(".name")!.textContent!.trim().toLowerCase(); + return titleB.localeCompare(titleA); + } + + case "name-asc": + default: { + const titleA = a.querySelector(".name")!.textContent!.trim().toLowerCase(); + const titleB = b.querySelector(".name")!.textContent!.trim().toLowerCase(); + return titleA.localeCompare(titleB); + } } - - return titleA.localeCompare(titleB); }); // Re-appending existing plugin nodes moves them into sorted order (no duplicates) From 54374b002a2ee8b7f18f2b86ba6801d370b73773 Mon Sep 17 00:00:00 2001 From: David Brett Date: Thu, 2 Apr 2026 12:56:48 +0200 Subject: [PATCH 3/7] refactor: extract sort config into pluginSortOptions --- src/pages/plugins/index.astro | 50 +++++++++++++------------- src/pages/plugins/pluginSortOptions.ts | 32 +++++++++++++++++ 2 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 src/pages/plugins/pluginSortOptions.ts diff --git a/src/pages/plugins/index.astro b/src/pages/plugins/index.astro index 70bba0a..3c2a834 100644 --- a/src/pages/plugins/index.astro +++ b/src/pages/plugins/index.astro @@ -8,6 +8,10 @@ import SectionSubheader from "@/components/SectionSubheader.astro"; import LabelValue from "@/components/plugins/LabelValue.astro"; import {TAGS} from "@/constants"; import featuredPluginIds from "@/data/featured-plugins.yml"; +import { + DEFAULT_PLUGIN_SORT, + PLUGIN_SORT_LIST, +} from "@/pages/plugins/pluginSortOptions"; import Swiper from "@/components/Swiper.astro"; const plugins = await getPluginsJson(); @@ -48,10 +52,11 @@ const hasFeaturedPlugins = featured.length > 0; @@ -112,6 +117,11 @@ const hasFeaturedPlugins = featured.length > 0; diff --git a/src/pages/plugins/pluginSortOptions.ts b/src/pages/plugins/pluginSortOptions.ts new file mode 100644 index 0000000..8908f25 --- /dev/null +++ b/src/pages/plugins/pluginSortOptions.ts @@ -0,0 +1,32 @@ +export const PLUGIN_SORTS = { + NAME_ASC: { + value: "name-asc", + label: "Name (A-Z)", + }, + NAME_DESC: { + value: "name-desc", + label: "Name (Z-A)", + }, + FIRST_RELEASE_DESC: { + value: "first-release-desc", + label: "Release Date", + }, + LATEST_RELEASE_DESC: { + value: "latest-release-desc", + label: "Last Updated", + }, +} as const; + +export const PLUGIN_SORT_LIST = Object.values(PLUGIN_SORTS); + +export type PluginSortValue = typeof PLUGIN_SORTS[keyof typeof PLUGIN_SORTS]["value"]; + +export const DEFAULT_PLUGIN_SORT: PluginSortValue = PLUGIN_SORTS.NAME_ASC.value; + +const PLUGIN_SORT_VALUE_SET = new Set( + PLUGIN_SORT_LIST.map(option => option.value), +); + +export function isPluginSortValue(value: string): value is PluginSortValue { + return PLUGIN_SORT_VALUE_SET.has(value); +} \ No newline at end of file From e09e94d3418bb5a6988d54bd94875dd8eb4e0110 Mon Sep 17 00:00:00 2001 From: David Brett Date: Thu, 2 Apr 2026 13:12:14 +0200 Subject: [PATCH 4/7] style: adjust grid gaps in controls for new sort option - Reduce row gap to match padding - Reduce column gap to match plugin grid gap --- src/pages/plugins/index.astro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/plugins/index.astro b/src/pages/plugins/index.astro index 3c2a834..5acf0f1 100644 --- a/src/pages/plugins/index.astro +++ b/src/pages/plugins/index.astro @@ -79,7 +79,8 @@ const hasFeaturedPlugins = featured.length > 0; .controls { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 24px; + column-gap: 20px; + row-gap: 12px; margin-top: 24px; position: sticky; top: 68px; From b85b1e3d984d47cba28133ced5c76cc2d1b75795 Mon Sep 17 00:00:00 2001 From: David Brett Date: Thu, 2 Apr 2026 16:24:01 +0200 Subject: [PATCH 5/7] fix: plugin sort value no longer shown in url if equal to the default --- src/pages/plugins/index.astro | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/plugins/index.astro b/src/pages/plugins/index.astro index 5acf0f1..fd06b56 100644 --- a/src/pages/plugins/index.astro +++ b/src/pages/plugins/index.astro @@ -186,7 +186,11 @@ function updateUrlParams(): void { url.searchParams.delete("tags"); } - url.searchParams.set("sort", sortValue); + if (sortValue !== DEFAULT_PLUGIN_SORT){ + url.searchParams.set("sort", sortValue); + } else { + url.searchParams.delete("sort"); + } history.replaceState(null, "", url); } From de5c4412b5ed4bc4250e0e2cc7af37e366246d51 Mon Sep 17 00:00:00 2001 From: David Brett Date: Thu, 2 Apr 2026 16:26:27 +0200 Subject: [PATCH 6/7] fix: remove unnecessary first sort on plugin page when set to default sort --- src/pages/plugins/index.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/plugins/index.astro b/src/pages/plugins/index.astro index fd06b56..ae979b1 100644 --- a/src/pages/plugins/index.astro +++ b/src/pages/plugins/index.astro @@ -154,7 +154,7 @@ tagsSelect.value = tagsParam; sortSelect.value = sortParam; filterPlugins(); -applySort(); +if(sortParam != DEFAULT_PLUGIN_SORT){applySort();} updateUrlParams(); function onFilterChange(): void { From c0edb948260f6dda3f40ad3976025f0350242b1b Mon Sep 17 00:00:00 2001 From: David Brett Date: Wed, 8 Apr 2026 20:47:06 +0200 Subject: [PATCH 7/7] feat: simplify sort options - Remove Name (Z-A) sort - Rename "Name (A-Z)" to "Name" - Rename "Last Updated" to "Updated Date" --- src/pages/plugins/index.astro | 6 ------ src/pages/plugins/pluginSortOptions.ts | 8 ++------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/pages/plugins/index.astro b/src/pages/plugins/index.astro index ae979b1..6b8a442 100644 --- a/src/pages/plugins/index.astro +++ b/src/pages/plugins/index.astro @@ -254,12 +254,6 @@ function applySort(): void { return firstB - firstA; } - case PLUGIN_SORTS.NAME_DESC.value: { - const titleA = a.querySelector(".name")!.textContent!.trim().toLowerCase(); - const titleB = b.querySelector(".name")!.textContent!.trim().toLowerCase(); - return titleB.localeCompare(titleA); - } - case PLUGIN_SORTS.NAME_ASC.value: default: { const titleA = a.querySelector(".name")!.textContent!.trim().toLowerCase(); diff --git a/src/pages/plugins/pluginSortOptions.ts b/src/pages/plugins/pluginSortOptions.ts index 8908f25..c95a8da 100644 --- a/src/pages/plugins/pluginSortOptions.ts +++ b/src/pages/plugins/pluginSortOptions.ts @@ -1,11 +1,7 @@ export const PLUGIN_SORTS = { NAME_ASC: { value: "name-asc", - label: "Name (A-Z)", - }, - NAME_DESC: { - value: "name-desc", - label: "Name (Z-A)", + label: "Name", }, FIRST_RELEASE_DESC: { value: "first-release-desc", @@ -13,7 +9,7 @@ export const PLUGIN_SORTS = { }, LATEST_RELEASE_DESC: { value: "latest-release-desc", - label: "Last Updated", + label: "Updated Date", }, } as const;