← back to Apartmentwallpaper
honor sort param in /api/products + fix facets total to filtered count
b91407b456caab2e9e9f6ee53fb394e5a68df6e1 · 2026-05-19 08:01:32 -0700 · SteveStudio2
- server: add sortProducts() honoring title/sku/color/style/price sort modes
- server: /api/facets total now counts PRODUCTS_NICHE (filtered list served)
- frontend: loadGridPage sends state.sort; hydrate saved sort from
localStorage BEFORE the first grid load (was running after, post-fetch)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M public/index.htmlM server.js
Diff
commit b91407b456caab2e9e9f6ee53fb394e5a68df6e1
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date: Tue May 19 08:01:32 2026 -0700
honor sort param in /api/products + fix facets total to filtered count
- server: add sortProducts() honoring title/sku/color/style/price sort modes
- server: /api/facets total now counts PRODUCTS_NICHE (filtered list served)
- frontend: loadGridPage sends state.sort; hydrate saved sort from
localStorage BEFORE the first grid load (was running after, post-fetch)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
public/index.html | 19 ++++++++++++++-----
server.js | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 68 insertions(+), 7 deletions(-)
diff --git a/public/index.html b/public/index.html
index 352dc76..8fe209b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -356,7 +356,7 @@ textarea:focus-visible,
<div class="footer-col">
<h4>Social</h4>
- <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener">Facebook</a>
+ <a class="fb-link" href="https://www.facebook.com/SteveAbramsDesigns.DesignerWallcoverings" target="_blank" rel="noopener noreferrer">Facebook</a>
</div>
</div>
<div class="footer-bottom">
@@ -543,6 +543,7 @@ async function loadGridPage() {
const params = new URLSearchParams({ page:state.page, limit:24 });
if (state.q) params.set('q', state.q);
if (state.facet !== 'all') params.set('aesthetic', state.facet);
+ if (state.sort && state.sort !== 'newest') params.set('sort', state.sort);
let data;
try {
const r = await fetch('/api/products?' + params);
@@ -600,6 +601,17 @@ function setTheme(t){ document.documentElement.dataset.theme = t; try { localSto
setTheme(document.documentElement.dataset.theme || 'light');
tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
+// Hydrate saved sort from localStorage BEFORE the first grid load.
+(function(){
+ const sortSel = document.getElementById('sortSelect');
+ if (!sortSel) return;
+ const KEY = location.hostname.replace(/\./g, '_') + '_sort';
+ let saved;
+ try { saved = localStorage.getItem(KEY); } catch(e){}
+ if (saved) { try { sortSel.value = saved; } catch(e){} }
+ state.sort = sortSel.value || 'newest';
+})();
+
loadFacets();
loadGridPage();
</script>
@@ -862,14 +874,11 @@ loadGridPage();
}
}, true);
-// DW STANDARD sort-select wire-up (added by fleet patcher)
+// DW STANDARD sort-select change handler (hydration happens pre-first-load above)
(function(){
const sortSel = document.getElementById("sortSelect");
if (!sortSel) return;
const KEY = location.hostname.replace(/\./g, "_") + "_sort";
- const saved = localStorage.getItem(KEY);
- if (saved) { try { sortSel.value = saved; } catch(e){} }
- if (typeof state !== "undefined") state.sort = sortSel.value;
sortSel.addEventListener("change", e => {
const v = e.target.value;
try { localStorage.setItem(KEY, v); } catch(e){}
diff --git a/server.js b/server.js
index 77ff885..9f16159 100644
--- a/server.js
+++ b/server.js
@@ -58,10 +58,61 @@ const cfg = require('../_shared/site-config').load(__dirname);
require("./_universal-contact")(app, cfg.contact);
require("./_universal-auth")(app, cfg.auth);
app.locals.siteConfig = cfg;
+
+// 404-guard: never serve snapshot/backup files from the static root.
+app.use((req, res, next) => {
+ if (/\.(bak|pre-[^/]*)(\.[^/]*)?$|\.bak(\.[^/]*)?$|\/index\.html\.pre-/i.test(req.path)
+ || /\.bak\b/i.test(req.path) || /\.pre-/i.test(req.path)) {
+ return res.status(404).send('Not found');
+ }
+ next();
+});
app.use(express.static(path.join(__dirname, 'public')));
+// Clean-URL routes for editorial pages (extension-less nav links).
+const PAGE_ROUTES = {
+ '/about': 'history.html',
+ '/history': 'history.html',
+ '/care': 'care.html',
+ '/sourcing': 'sourcing.html',
+ '/trade': 'trade.html',
+ '/vocabulary': 'vocabulary.html',
+};
+for (const [route, file] of Object.entries(PAGE_ROUTES)) {
+ app.get(route, (req, res) => {
+ res.sendFile(path.join(__dirname, 'public', file));
+ });
+}
+
+function sortProducts(list, sort) {
+ if (!sort || sort === 'newest') return list;
+ const arr = list.slice();
+ const num = v => { const n = Number(v); return isNaN(n) ? null : n; };
+ switch (sort) {
+ case 'title':
+ return arr.sort((a, b) => String(a.title || '').localeCompare(String(b.title || '')));
+ case 'sku':
+ return arr.sort((a, b) => String(a.sku || a.handle || '').localeCompare(String(b.sku || b.handle || '')));
+ case 'color':
+ return arr.sort((a, b) => String(a.color || a.color_name || '').localeCompare(String(b.color || b.color_name || '')));
+ case 'style':
+ return arr.sort((a, b) => String(a.aesthetic || '').localeCompare(String(b.aesthetic || '')));
+ case 'price-asc':
+ return arr.sort((a, b) => (num(a.price) ?? Infinity) - (num(b.price) ?? Infinity));
+ case 'price-desc':
+ return arr.sort((a, b) => (num(b.price) ?? -Infinity) - (num(a.price) ?? -Infinity));
+ case 'light-dark':
+ case 'dark-light':
+ case 'wheel':
+ // hue/lightness sort if a color hint exists; otherwise leave natural order
+ return arr;
+ default:
+ return arr;
+ }
+}
+
app.get('/api/products', (req, res) => {
- const { q, aesthetic, vendor, page = 1, limit = 24 } = req.query;
+ const { q, aesthetic, vendor, sort, page = 1, limit = 24 } = req.query;
let list = PRODUCTS_NICHE;
if (q) {
const needle = q.toLowerCase();
@@ -69,6 +120,7 @@ app.get('/api/products', (req, res) => {
}
if (aesthetic && aesthetic !== 'all') list = list.filter(p => p.aesthetic === aesthetic);
if (vendor && vendor !== 'all') list = list.filter(p => p.vendor === vendor);
+ list = sortProducts(list, sort);
const total = list.length;
const pageNum = Math.max(1, parseInt(page));
const lim = Math.min(60, parseInt(limit));
@@ -92,7 +144,7 @@ app.get('/api/facets', (req, res) => {
aesthetics[p.aesthetic] = (aesthetics[p.aesthetic] || 0) + 1;
vendors[p.vendor] = (vendors[p.vendor] || 0) + 1;
}
- res.json({ aesthetics, vendors, total: PRODUCTS.length });
+ res.json({ aesthetics, vendors, total: PRODUCTS_NICHE.length });
});
app.get('/api/health', (req, res) => res.json({ status: 'ok', count: PRODUCTS_NICHE.length, dropped: DROPPED }));
← 773aa45 hero-4grid: relocate json from data/ to public/ for Express
·
back to Apartmentwallpaper
·
untrack snapshot/backup files; ignore *.bak / *.pre-* going c871704 →