Add URL parameter filtering to Contributors page with full SEO preservation
Contributors page now supports dynamic filtering by project (?project=glossary) and role (?role=project-manager) while keeping all data in static HTML for search engine indexing.
Changes
Data generation (tenzing.py)
- Extract and normalize project/role names to lowercase-hyphenated format (
"Replications & Reversals"→"replications-and-reversals") - Generate HTML list items with
data-projectsanddata-rolesattributes containing comma-separated normalized values - Apply HTML attribute escaping to prevent injection
Template (tenzing_template.md)
- Add filter control UI (hidden until URL params present)
- Wrap contributor list in
<ul id="contributor-list">for JavaScript access
Client-side filtering (contributor-filter.js)
- Parse URL parameters and filter DOM nodes matching data attributes
- Display filtered results with count and active filter info
- Provide clear filters button to return to full list
Implementation
<!-- Generated output structure -->
<ul id="contributor-list">
<li data-projects="glossary,impact-on-students"
data-roles="writing---original-draft,investigation">
<strong><a href="https://orcid.org/...">Jane Doe</a></strong>
contributed to <a href="...">Glossary</a> with <em>Writing</em>...
</li>
</ul>
All contributor data remains in static HTML before JavaScript executes, ensuring search engines index everything. Filtering is purely additive client-side enhancement.
Usage Examples
/contributors # Full list
/contributors?project=glossary # Glossary contributors only
/contributors?role=project-manager # All project managers
/contributors?project=glossary&role=writing---original-draft # Combined filter
Screenshots
Filtered by Project (Glossary)
Combined Filter (Project + Role)
Security
- XSS prevention via input sanitization (
escapeHtml()) - HTML attribute escaping in Python (
html.escape()) - Safe DOM manipulation (no
innerHTMLinjection) - CodeQL: 0 vulnerabilities
Documentation
-
CONTRIBUTORS_FILTERING.md- User guide with normalization rules and examples -
scripts/forrt_contribs/README.md- Script usage for regenerating page -
IMPLEMENTATION_NOTES.md- Architecture and maintenance notes
Deployment
To activate filtering on existing contributors data:
cd scripts/forrt_contribs
python3 tenzing.py
cp tenzing.md ../../content/contributors/tenzing.md
Original prompt
This section details on the original issue you should resolve
<issue_title>Add project- and role-based filtering to Contributors page</issue_title> <issue_description>Summary
The current Contributors page (tenzing.md) presents a comprehensive, static list of all FORRT contributors. We want to enhance it by allowing users to filter dynamically by project (e.g., /contributors?project=glossary) and optionally by role (e.g., /contributors?role=project-lead) — without losing search-engine visibility of individual names.
This feature will require adjustments to both the data generation script and the website layout.
Goals / Deliverables
Data generation enhancements
Extend the existing tenzing.py script so that the generated Markdown includes lightweight HTML attributes or data tags per contributor entry:
Balazs Aczel contributed to ... Ensure each project and role are normalized (lowercase, hyphen-safe) for consistent filtering.
Keep the generated file fully static (for SEO), but semantically structured for client-side filtering.
Website display logic
On page load, check for project and/or role parameters in the URL.
If parameters are present:
Hide the full contributor list.
Dynamically render a focused list of contributors matching the filter, e.g.:
Project: Glossary
Balazs Aczel — Project co-lead
Maria Lopez — WritingIf no parameters are provided, display the full unfiltered overview (contributors → projects → roles), preserving the current hierarchy.
Optional UI features
Add basic dropdowns or buttons for project and role selection that update the URL (?project= / ?role=) without reloading.
Maintain accessibility and crawlability — all data must exist in the static HTML before filtering.
Technical constraints
No reduction in search engine indexability (all names and projects remain visible in the static HTML).
The dynamic filtering is purely client-side (no new API or server-side filtering).
Should work seamlessly with existing Hugo build output and templating.
Deliverables checklist
Updated tenzing.py script outputting data-projects and data-roles attributes.
Modified template (tenzing_template.md) with placeholders ready for those attributes.
JS file for URL parameter parsing and DOM filtering logic.
Verified filtered view layout (project-centric, role-centric).
Maintained full SEO visibility.
Success criteria
Visiting /contributors?project=glossary displays only contributors involved in “Glossary” with clean name–role pairs.
Visiting /contributors?role=project-lead lists all contributors serving as project leads, across projects.
Viewing /contributors without parameters shows the full static list.
Search engines can still index all contributor names and text.</issue_description>
Comments on the Issue (you are @copilot in this section)
- Fixes forrtproject/forrtproject.github.io#497
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.
:thumbsup: All image files/references (if any) are in webp format, in line with our policy.
⚠️ This PR was attempted to be deployed to staging as part of an aggregated deployment, but had merge conflicts and was skipped. Please resolve conflicts and try again. View staging at: https://staging.forrt.org
⚠️ This PR was attempted to be deployed to staging as part of an aggregated deployment, but had merge conflicts and was skipped. Please resolve conflicts and try again. View staging at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org
This needs local testing - I'm afraid that running data processing on this branch will lead to an artefact that break master deployment?
✅ This PR has been deployed to staging as part of an aggregated deployment. View at: https://staging.forrt.org