Skip to content

Add client-side search, filter, and sort for items#116

Open
jeroenjanssens wants to merge 21 commits intomainfrom
search-filter-sort
Open

Add client-side search, filter, and sort for items#116
jeroenjanssens wants to merge 21 commits intomainfrom
search-filter-sort

Conversation

@jeroenjanssens
Copy link
Copy Markdown
Collaborator

@jeroenjanssens jeroenjanssens commented Apr 2, 2026

Summary

Add client-side search, filtering, and sorting to the blog, software, events, resources, category, and tag pages so visitors can quickly find what they're looking for.

Features

  • Debounced search across title, description, tags, software, authors, and location with quoted phrase support and accent normalization
  • Dropdown multiselect filters (topics, languages, type) with checkmarks, count badges, and URL state persistence
  • Multiple sort modes (date, title, stars, views, duration, location) with optimized DOM reordering
  • Always-visible toolbar with item count, Show/Hide Filters toggle, and conditional Reset Filters button
  • Smart scroll: scrolls to show first card below sticky controls only when user has scrolled past them
  • Progressive enhancement: filter controls hidden by default and revealed via JS; no-JS users never download the metadata JSON
  • Sticky controls gain a bottom border via IntersectionObserver when pinned

Performance

  • Card metadata generated as separate card-index.json files via a Hugo CardIndex output format, fetched asynchronously — removes ~300 bytes of data-* attributes per card from the HTML
  • Repeated Tailwind class strings extracted into CSS component classes — reduces HTML size by ~19% overall (4.6 MB), with the biggest impact on the videos page

Pages with search/filter/sort

  • Blog — search, sort (date, title), filter by topic and language
  • Software — search, sort (title, stars, first commit), filter by language
  • Events — search, sort (date, title, location), filter by language; section headings (current/upcoming/past) preserved on default sort
  • Resources (videos, cheatsheets) — search, sort (date, title, views, duration), filter by language (cheatsheets)
  • Category term pages — search, sort (date, title), filter by type
  • Tag term pages — search, sort (date, title), filter by type

Key files

  • assets/js/search-filter-sort.js — CardManager class (IIFE, no dependencies)
  • layouts/partials/filter-controls.html — Reusable filter UI with dropdown multiselects
  • layouts/partials/card-index-entry.html — Generates per-card metadata for JSON index
  • assets/css/components.css — CSS component classes extracted from item.html

Test plan

  • Verify search works across all pages (blog, software, events, videos, categories, tags)
  • Test quoted phrase search (e.g., "machine learning")
  • Test dropdown multiselect filters: checkmarks toggle, count badges update, OR logic within groups
  • Test language alias ("JavaScript" includes TypeScript) and "Other" logic
  • Test sort dropdown changes order correctly
  • Test URL state persists on reload (search, sort, filters, showFilters)
  • Test Show/Hide Filters button toggles controls and updates label
  • Test Reset Filters button appears only when filters are active
  • Test events section headings show on default date sort, hide on other sorts
  • Test events sort order matches between Hugo and JS (no card jump on interaction)
  • Test no-JS fallback: controls hidden, JSON not fetched
  • Test mobile: responsive grid, dropdown positioning

Replace Alpine.js and server-side pagination with a vanilla JS CardManager
that hydrates data-* attributes from Hugo-rendered cards. Supports debounced
search (with quoted phrase matching), pill-based OR filters, multiple sort
modes, URL state persistence, and sticky filter controls with smart scroll.

Applied to blog, software, events, and resource/video pages.
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 2, 2026

Deploy Preview for posit-open-source canceled.

Name Link
🔨 Latest commit bd96737
🔍 Latest deploy log https://app.netlify.com/projects/posit-open-source/deploys/69d26a10683a07000879b1c7

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

@github-actions github-actions bot temporarily deployed to pull request April 2, 2026 15:45 Inactive
@jeroenjanssens jeroenjanssens changed the title Add client-side search, filter, and sort for card grids Add client-side search, filter, and sort for items Apr 2, 2026
- Hide filter controls by default, reveal via JS (progressive enhancement)
- Add bottom border when controls are in sticky mode via IntersectionObserver
- Fix bug where cards stayed in wrong order after clearing a URL search query
  by sorting all cards during reorder, not just visible ones
Card metadata is now generated as separate card-index.json files via a Hugo
CardIndex output format, fetched asynchronously by JavaScript. This removes
~300 bytes of data-* attributes per card from the HTML, reducing page size
significantly (especially for the videos page with 1,592 cards). No-JS users
never download the JSON. Cards are matched by their existing DOM id attribute.
Move frequently repeated Tailwind class strings from item.html into named
CSS component classes: .pill, .pill-link, .card-link, .card-badge,
.hero-icon, .hero-meta, .hero-meta-lg, .meta-item, .tile-meta.

Reduces HTML size by ~19% overall (4.6MB), with the biggest impact on the
videos page (-3.8MB / -21%).
@github-actions github-actions bot temporarily deployed to pull request April 4, 2026 10:59 Inactive
- Add important modifier to no-underline in .card-link so it wins over
  prose typography styles (fixes underlined text on featured blog card)
- Use container.querySelector instead of document.getElementById to find
  cards, so only cards inside the grid are managed by the filter/sort JS
- Add Language filter (Python, R, SQL, Rust, JavaScript, Other) to blog
- "JavaScript" also matches TypeScript via aliases config
- "Other" matches any language not in the known set (C, C++, Julia, etc.)
- Make title sort case-insensitive and ignore punctuation
Pre-compute a _sortTitle key in JS that strips everything except a-z, 0-9,
and spaces. This replaces the emoji-stripping regex in the Hugo template
and the localeCompare options, giving cleaner alphabetical sorting.
Replace per-section grids with a single filterable grid on category and
tag pages. Add a Type filter (blog post, software, event, video,
cheatsheet) so users can narrow by item type. Enable CardIndex output
for term pages and add card-index JSON templates for both taxonomies.
…sheets

- Add type field to card-index-entry.html, resolved from data/items.yaml
  badge_text (e.g., "blog post", "software", "event")
- Add language filter pills (Python, R, Other) to events page
- Add language filter pills (Python, R, Other) to cheatsheets page
Add an initially invisible button that JS reveals on load. Clicking it
toggles the filter controls panel. Uses invisible instead of hidden to
prevent page reflow when the button appears.
- Wire up Filters toggle button with showFilters URL parameter
- Auto-show controls when URL has active filters or showFilters=true
- Defer sorting on page load: preserve Hugo-rendered order unless a sort
  param is in the URL, avoiding unnecessary DOM reordering
- Show default sort option in dropdown without triggering a sort
- Normalize accented characters to ASCII (via NFD decomposition) in
  search index and sort keys so e.g. "Kalman" matches "Kálmán"
- Hydrate type field from card index JSON for type-based filtering
…okup

- Add inline script after filter controls that immediately shows the
  Filters button and opens controls when showFilters=true is in the URL,
  eliminating the flash before the main JS bundle loads
- Switch from previousElementSibling to parentNode.querySelector for
  finding filter elements, since the inline script tag breaks the
  sibling chain
- Only add showFilters=true to URL when filters are shown; omit when
  hidden to keep URLs clean
Include raw frontmatter software values in the card index JSON and the
JS search index so searching for package names always works, even when
the software taxonomy page doesn't exist.
@github-actions github-actions bot temporarily deployed to pull request April 4, 2026 13:50 Inactive
@github-actions github-actions bot temporarily deployed to pull request April 4, 2026 14:03 Inactive
Treat an empty sort key as equivalent to the default sort, so section
headings (Current/Upcoming/Past Events) display correctly on initial
render without requiring a manual reset.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot temporarily deployed to pull request April 5, 2026 06:43 Inactive
jeroenjanssens and others added 2 commits April 5, 2026 09:47
Events had no `date` frontmatter (only `start_date`), so Hugo's default
page ordering didn't match the JS date-desc sort, causing cards to jump
position on first interaction. Now all three groups (current, upcoming,
past) use consistent `sort ... ".Params.start_date" "desc"`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot temporarily deployed to pull request April 5, 2026 08:15 Inactive
- Search input takes full width on mobile, shares row on wider screens
- Hide "Showing" text and button icons on mobile to save space
- Rename sort labels: "Newest First" → "Date", "Title (A-Z)" → "Title"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot temporarily deployed to pull request April 5, 2026 09:39 Inactive
Extract duplicated filter/sort config from templates into YAML data
files (data/filters/*.yaml) as single source of truth. Templates now
load config via site.Data.filters and pass it to both the partial and
the data-filter-config attribute. Also refactors search-filter-sort.js
to pre-compute filter config map and default sort config.
Access pattern (site.Data.filters.blog etc.) stays the same.
@github-actions github-actions bot temporarily deployed to pull request April 5, 2026 10:59 Inactive
Rename card-index-entry.html to item-index-entry.html, *.cardindex.json
to *.itemindex.json, CardIndex output format to ItemIndex, and
data-card-container to data-item-container. Rename CardManager class to
ItemManager and cards array to items in JS. Keep "card" only where it
refers to the specific card display format (vs tile, row, hero).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant