@@ -45,6 +45,13 @@ const hasFeaturedPlugins = featured.length > 0;
4545 { TAGS .map (v => <option value = { v } >{ v } </option >)}
4646 </select >
4747 </LabelValue >
48+
49+ <LabelValue label =" Sort by" >
50+ <select aria-label =" Sort by" id =" sort" >
51+ <option value =" name-asc" selected >Name (A-Z)</option >
52+ <option value =" name-desc" >Name (Z-A)</option >
53+ </select >
54+ </LabelValue >
4855 </div >
4956 <div id =" store" class =" plugin-grid" >
5057 { plugins .map (v => <Plugin plugin = { v } />)}
@@ -60,7 +67,7 @@ const hasFeaturedPlugins = featured.length > 0;
6067
6168.controls {
6269 display: grid;
63- grid-template-columns: repeat(2 , 1fr);
70+ grid-template-columns: repeat(3 , 1fr);
6471 gap: 24px;
6572 margin-top: 24px;
6673 position: sticky;
@@ -101,36 +108,59 @@ import {TAGS} from "@/constants";
101108
102109const searchInput = document.querySelector("#search") as HTMLInputElement;
103110const tagsSelect = document.querySelector("#tags") as HTMLSelectElement;
111+ const sortSelect = document.querySelector("#sort") as HTMLSelectElement;
104112const notFound = document.querySelector("#not-found") as HTMLElement;
113+ const store = document.querySelector("#store") as HTMLElement;
105114
106115const plugins = document.querySelectorAll("#store > .plugin") as NodeListOf<HTMLElement>;
116+ const defaultSortValue = "name-asc";
117+ const allowedSortValues = ["name-asc", "name-desc"];
107118
108- searchInput.addEventListener("input", filterPlugins);
109- tagsSelect.addEventListener("change", filterPlugins);
119+ searchInput.addEventListener("input", onFilterChange);
120+ tagsSelect.addEventListener("change", onFilterChange);
121+ sortSelect.addEventListener("change", onSortChange);
110122
111123const url = new URL(window.location.href);
112124
113125const searchParam = url.searchParams.get("search") ?? "";
114126let tagsParam: any = url.searchParams.get("tags") ?? "";
127+ let sortParam = url.searchParams.get("sort") ?? defaultSortValue;
115128
116129if (!TAGS.includes(tagsParam)) {
117130 tagsParam = "";
118131}
119132
133+ if (!allowedSortValues.includes(sortParam)) {
134+ sortParam = defaultSortValue;
135+ }
136+
120137searchInput.value = searchParam;
121138tagsSelect.value = tagsParam;
139+ sortSelect.value = sortParam;
122140
123141filterPlugins();
142+ applySort();
143+ updateUrlParams();
124144
125- function filterPlugins(): void {
126- let foundAtLeastOnePlugin = false;
145+ function onFilterChange(): void {
146+ filterPlugins();
147+ applySort();
148+ updateUrlParams();
149+ }
127150
128- const searchValue = searchInput.value.toLowerCase();
151+ function onSortChange(): void {
152+ applySort();
153+ updateUrlParams();
154+ }
155+
156+ function updateUrlParams(): void {
157+ const searchValue = searchInput.value;
129158 const tagsValue = tagsSelect.value;
159+ const sortValue = sortSelect.value;
130160
131161 const url = new URL(window.location.href);
132162 if (searchValue.length > 0) {
133- url.searchParams.set("search", searchInput.value );
163+ url.searchParams.set("search", searchValue );
134164 } else {
135165 url.searchParams.delete("search");
136166 }
@@ -141,7 +171,20 @@ function filterPlugins(): void {
141171 url.searchParams.delete("tags");
142172 }
143173
174+ if (allowedSortValues.includes(sortValue)) {
175+ url.searchParams.set("sort", sortValue);
176+ } else {
177+ url.searchParams.delete("sort");
178+ }
179+
144180 history.replaceState(null, "", url);
181+ }
182+
183+ function filterPlugins(): void {
184+ let foundAtLeastOnePlugin = false;
185+
186+ const searchValue = searchInput.value.toLowerCase();
187+ const tagsValue = tagsSelect.value;
145188
146189 for (const plugin of plugins) {
147190 const title = plugin.querySelector(".name")!.textContent!.toLowerCase();
@@ -168,4 +211,26 @@ function filterPlugins(): void {
168211 notFound.style.display = "block";
169212 }
170213}
214+
215+ function applySort(): void {
216+ const sortValue = sortSelect.value;
217+
218+ if (!allowedSortValues.includes(sortValue)) {
219+ return;
220+ }
221+
222+ const sortedPlugins = Array.from(plugins).sort((a, b) => {
223+ const titleA = a.querySelector(".name")!.textContent!.trim().toLowerCase();
224+ const titleB = b.querySelector(".name")!.textContent!.trim().toLowerCase();
225+
226+ if (sortValue === "name-desc") {
227+ return titleB.localeCompare(titleA);
228+ }
229+
230+ return titleA.localeCompare(titleB);
231+ });
232+
233+ // Re-appending existing plugin nodes moves them into sorted order (no duplicates)
234+ store.append(...sortedPlugins);
235+ }
171236</script >
0 commit comments