Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ content.
### Research tag filtering

- Client-side JS filtering plus pre-generated tag pages for SEO.
- Tags must use `<div class="tags"><span>TagName</span></div>` in
- Tags must use `<div class="tags"><button type="button">TagName</button></div>` in
`_research/index.md`.

### Redirect pages
Expand Down Expand Up @@ -133,7 +133,7 @@ Add to `_research/index.md` in this exact form:
```markdown
<h3 id="NUMBER">[NUMBER] Author1, A., **Author2, B.**, & Author3, C. Title. _Journal_, Volume, Pages (Year).</h3>

<div class="tags"><span>Tag1</span><span>Tag2</span><span>Featured</span></div>
<div class="tags"><button type="button" aria-pressed="false">Tag1</button><button type="button" aria-pressed="false">Tag2</button><button type="button" aria-pressed="false">Featured</button></div>

[![Badge](https://img.shields.io/static/v1.svg?style=flat-square&label=LABEL&message=MESSAGE&color=COLOR)](URL)
```
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ present:
```markdown
<h3 id="NUMBER">[NUMBER] Author1, A., **Author2, B.**, & Author3, C. Title. _Journal_, Volume, Pages (Year).</h3>

<div class="tags"><span>Tag1</span><span>Tag2</span><span>Featured</span></div>
<div class="tags"><button type="button" aria-pressed="false">Tag1</button><button type="button" aria-pressed="false">Tag2</button><button type="button" aria-pressed="false">Featured</button></div>

[![Badge1](https://img.shields.io/static/v1.svg?style=flat-square&label=LABEL&message=MESSAGE&color=COLOR)](URL)
[![Badge2](https://img.shields.io/static/v1.svg?style=flat-square&label=LABEL&message=MESSAGE&color=COLOR)](URL)
Expand Down Expand Up @@ -547,7 +547,7 @@ The search functionality includes content from our external blog at blogs.comphy
Research papers can be tagged with multiple topics. Tags are defined in the markdown files using the following format:

```html
<div class="tags"><span>Tag1</span><span>Tag2</span></div>
<div class="tags"><button type="button" aria-pressed="false">Tag1</button><button type="button" aria-pressed="false">Tag2</button></div>
```

These tags are:
Expand Down
2 changes: 2 additions & 0 deletions _layouts/research.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ <h3>Contents</h3>
/* Hidden paper (filtered out) */
.research-content .paper-container.hidden { display: none; }
/* Active tag (clicked) gets a teal solid treatment */
.research-content .tags button.active,
.research-content .tags button[aria-pressed="true"],
.research-content .tags span.active,
.r-theme__tag.active {
background: var(--c-accent-teal);
Expand Down
94 changes: 47 additions & 47 deletions _research/index.md

Large diffs are not rendered by default.

44 changes: 31 additions & 13 deletions assets/css/research-v2.css
Original file line number Diff line number Diff line change
Expand Up @@ -509,12 +509,12 @@
flow horizontally and wrap. The `_research/index.md` sort-by-topic
block uses one `.tags` div per tag; inline-flex lets them queue up
on a single line instead of stacking. Per-paper `.tags` rows still
look right because each contains its own spans in one div. */
look right because each contains its own buttons in one div. */
.research-content .tags {
display: inline-flex;
flex-wrap: wrap;
gap: 6px;
margin: 0 6px 6px 0;
gap: var(--s-2);
margin: 0 var(--s-2) var(--s-2) 0;
vertical-align: top;
}

Expand All @@ -533,30 +533,48 @@
margin: var(--s-2) 0 var(--s-3);
}

.research-content .tags button,
.research-content .tags span {
appearance: none;
display: inline-flex;
align-items: center;
padding: 4px 10px;
background: color-mix(in srgb, var(--c-brand-purple) 10%, transparent);
color: var(--c-brand-purple);
border: 1px solid color-mix(in srgb, var(--c-brand-purple) 22%, transparent);
margin: 0;
padding: var(--s-1) var(--s-3);
background: color-mix(in srgb, var(--c-accent-teal) 10%, transparent);
color: var(--c-accent-teal);
border: 1px solid color-mix(in srgb, var(--c-accent-teal) 22%, transparent);
border-radius: var(--r-pill);
font-family: var(--t-sans);
font-weight: 500;
font-size: 12px;
letter-spacing: 0.02em;
font-weight: var(--t-weight-med);
font-size: var(--t-eyebrow);
line-height: 1.6;
white-space: nowrap;
cursor: pointer;
transition: background var(--dur-fast) var(--ease);
transition:
background var(--dur-fast) var(--ease),
color var(--dur-fast) var(--ease),
border-color var(--dur-fast) var(--ease);
}

.research-content .tags button:hover,
.research-content .tags span:hover {
background: color-mix(in srgb, var(--c-accent-teal) 15%, transparent);
color: var(--c-accent-teal);
border-color: var(--c-accent-teal);
}

[data-theme="dark"] .research-content .tags span {
color: #d99adc;
.research-content .tags button:focus-visible,
.research-content .tags span:focus-visible {
outline: var(--s-1) solid var(--c-accent-teal);
outline-offset: var(--s-1);
}

.research-content .tags button[aria-pressed="true"],
.research-content .tags button.active,
.research-content .tags span.active {
background: var(--c-accent-teal);
color: var(--c-accent-teal-fg);
border-color: var(--c-accent-teal);
}

/* Shield badges inline. Long DOI badges are wider than a 390 px
Expand Down
7 changes: 5 additions & 2 deletions assets/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1938,7 +1938,9 @@ section#featured:after,
margin: 1rem 0;
}

.featured-paper .tags span {
.featured-paper .tags span,
.featured-paper .tags button {
appearance: none;
display: inline-block;
padding: 0.4rem 0.8rem;
background: var(--tag-bg);
Expand All @@ -1951,7 +1953,8 @@ section#featured:after,
border: 1px solid var(--tag-border);
}

.featured-paper .tags span:hover {
.featured-paper .tags span:hover,
.featured-paper .tags button:hover {
background: var(--tag-bg-hover);
transform: translateY(-2px);
border-color: var(--tag-border-hover);
Expand Down
6 changes: 4 additions & 2 deletions assets/js/command-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,9 @@
window.matchMedia("(prefers-color-scheme: dark)").matches;

// Collect all unique tags from the page
const tagElements = document.querySelectorAll(".tags span");
const tagElements = document.querySelectorAll(
".tags button, .tags span"
);
const tags = new Set();
tagElements.forEach((tag) => {
tags.add(tag.textContent);
Expand Down Expand Up @@ -656,7 +658,7 @@
// Find the actual tag in the document and simulate a click on it
const tagText = btn.textContent;
const matchingTag = Array.from(
document.querySelectorAll(".tags span")
document.querySelectorAll(".tags button, .tags span")
).find((tag) => tag.textContent === tagText);

if (matchingTag) {
Expand Down
39 changes: 26 additions & 13 deletions assets/js/page-research.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ document.addEventListener("DOMContentLoaded", function () {
}
});

var contentTags = researchContent.querySelectorAll(".tags span");
var contentTags = researchContent.querySelectorAll(
".tags button, .tags span"
);
var themeTags = document.querySelectorAll(".r-theme__tag[data-filter-tag]");
var tagControls = Array.from(contentTags).concat(Array.from(themeTags));
var papers = researchContent.querySelectorAll(".paper-container");
Expand All @@ -86,9 +88,16 @@ document.addEventListener("DOMContentLoaded", function () {
return control.getAttribute("data-filter-tag") || control.textContent;
}

function setPressed(control, pressed) {
control.classList.toggle("active", pressed);
if (control.tagName === "BUTTON") {
control.setAttribute("aria-pressed", pressed ? "true" : "false");
}
}

function setActive(tagText) {
tagControls.forEach(function (control) {
control.classList.toggle("active", getTagText(control) === tagText);
setPressed(control, getTagText(control) === tagText);
});
}

Expand All @@ -103,7 +112,7 @@ document.addEventListener("DOMContentLoaded", function () {
options = options || {};
activeTag = null;
tagControls.forEach(function (control) {
control.classList.remove("active");
setPressed(control, false);
});
papers.forEach(function (paper) {
paper.classList.remove("hidden");
Expand All @@ -119,7 +128,7 @@ document.addEventListener("DOMContentLoaded", function () {
var tags = paper.querySelector(".tags");
var match =
tags &&
Array.from(tags.querySelectorAll("span")).some(function (tag) {
Array.from(tags.querySelectorAll("button, span")).some(function (tag) {
return tag.textContent === tagText;
});
paper.classList.toggle("hidden", !match);
Expand All @@ -137,16 +146,20 @@ document.addEventListener("DOMContentLoaded", function () {
control.addEventListener("click", function () {
activateTag(control);
});
if (control.tagName !== "BUTTON") {
control.setAttribute("role", "button");
control.setAttribute("tabindex", "0");
control.addEventListener("keydown", function (event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
activateTag(control);
}
});
if (control.tagName === "BUTTON") {
if (!control.hasAttribute("aria-pressed")) {
control.setAttribute("aria-pressed", "false");
}
return;
}
control.setAttribute("role", "button");
control.setAttribute("tabindex", "0");
control.addEventListener("keydown", function (event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
activateTag(control);
}
});
}

tagControls.forEach(bindTagControl);
Expand Down
2 changes: 1 addition & 1 deletion scripts/generate_filtered_research.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# Find all unique tags
all_tags = []
doc.css('.tags span').each do |tag|
doc.css('.tags button, .tags span').each do |tag|
all_tags << tag.text unless all_tags.include?(tag.text)
end

Expand Down
12 changes: 6 additions & 6 deletions tests/command-actions-workflow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@ describe("command palette action workflows", () => {
"beforeend",
`
<div class="research-content">
<h3 id="24">Paper A</h3><div class="tags"><span>Elastic</span></div>
<h3 id="23">Paper B</h3><div class="tags"><span>Newtonian</span><span>Elastic</span></div>
<h3 id="22">Paper C</h3><div class="tags"><span>Capillary</span></div>
<h3 id="21">Paper D</h3><div class="tags"><span>Inertial</span></div>
<h3 id="20">Paper E</h3><div class="tags"><span>Viscous</span></div>
<h3 id="24">Paper A</h3><div class="tags"><button type="button">Elastic</button></div>
<h3 id="23">Paper B</h3><div class="tags"><button type="button">Newtonian</button><button type="button">Elastic</button></div>
<h3 id="22">Paper C</h3><div class="tags"><button type="button">Capillary</button></div>
<h3 id="21">Paper D</h3><div class="tags"><button type="button">Inertial</button></div>
<h3 id="20">Paper E</h3><div class="tags"><button type="button">Viscous</button></div>
</div>`
);
require("../assets/js/page-research.js");
Expand Down Expand Up @@ -137,7 +137,7 @@ describe("command palette action workflows", () => {
"beforeend",
`
<div class="tags">${["A", "B", "C", "D", "E"]
.map((tag) => `<span>${tag}</span>`)
.map((tag) => `<button type="button">${tag}</button>`)
.join("")}</div>`
);
initPage("/research/");
Expand Down
6 changes: 3 additions & 3 deletions tests/command-data-actual.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

// Mock dependencies
global.Fuse = jest.fn().mockImplementation(() => ({
search: jest.fn().mockReturnValue([])
search: jest.fn().mockReturnValue([]),
}));
global.sortCoursesByDate = jest.fn();

Expand Down Expand Up @@ -124,10 +124,10 @@ describe("command-data.js actual implementation", () => {
window.history.replaceState({}, "", "/research");
document.body.innerHTML = `
<div class="tags">
<span></span>
<button type="button"></button>
</div>
`;
document.querySelector(".tags span").textContent = "<img src=x>";
document.querySelector(".tags button").textContent = "<img src=x>";

require("../assets/js/command-data.js");

Expand Down
41 changes: 37 additions & 4 deletions tests/research-filter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ describe("research tag filtering user paths", () => {
<button class="r-theme__tag" data-filter-tag="Viscoelastic">Elasticity</button>
<div class="research-content">
<h2>Published work</h2>
<h3 id="24">Paper A</h3><div class="tags"><span>Viscoelastic</span></div>
<h3 id="24">Paper A</h3><div class="tags"><button type="button" aria-pressed="false">Viscoelastic</button></div>
<p>Elastic material</p>
<h3 id="23">Paper B</h3><div class="tags"><span>Newtonian</span></div>
<h3 id="23">Paper B</h3><div class="tags"><button type="button" aria-pressed="false">Newtonian</button></div>
<h2 id="methods">Methods</h2><p>Methods text</p>
</div>`;
require("../assets/js/page-research.js");
Expand Down Expand Up @@ -64,22 +64,49 @@ describe("research tag filtering user paths", () => {

test("published paper tags filter and a second activation clears", () => {
load();
const tag = document.querySelector(".tags span");
const tag = document.querySelector(".tags button");
expect(tag.tagName).toBe("BUTTON");
expect(visiblePapers()).toEqual(["24", "23"]);
tag.click();
expect(visiblePapers()).toEqual(["24"]);
expect(window.location.search).toBe("?tag=Viscoelastic");
expect(tag.getAttribute("aria-pressed")).toBe("true");
expect(
document.querySelector(".r-theme__tag").getAttribute("aria-pressed")
).toBe("true");
expect(document.querySelector(".r-theme__tag").classList).toContain(
"active"
);
tag.click();
expect(tag.getAttribute("aria-pressed")).toBe("false");
expect(visiblePapers()).toEqual(["24", "23"]);
expect(window.location.search).toBe("");
});

test("keyboard Enter and Space operate tags with stable deep links", () => {
load("?tag=Newtonian");
expect(visiblePapers()).toEqual(["23"]);
const tag = document.querySelector(".tags button");
expect(tag.getAttribute("type")).toBe("button");
expect(tag.tabIndex).toBe(0);
// Enter and Space activate a real button through click.
tag.click();
expect(tag.getAttribute("aria-pressed")).toBe("true");
expect(visiblePapers()).toEqual(["24"]);
tag.click();
expect(tag.getAttribute("aria-pressed")).toBe("false");
expect(visiblePapers()).toEqual(["24", "23"]);
});

test("legacy span chips still filter from Enter and Space", () => {
window.history.replaceState({}, "", "/research/");
document.body.innerHTML = `
<div class="research-content">
<h3 id="24">Paper A</h3><div class="tags"><span>Viscoelastic</span></div>
<h3 id="23">Paper B</h3><div class="tags"><span>Newtonian</span></div>
</div>`;
require("../assets/js/page-research.js");
ready();
const tag = document.querySelector(".tags span");
expect(tag.getAttribute("role")).toBe("button");
expect(tag.tabIndex).toBe(0);
Expand All @@ -91,7 +118,13 @@ describe("research tag filtering user paths", () => {
tag.dispatchEvent(enter);
expect(enter.defaultPrevented).toBe(true);
expect(visiblePapers()).toEqual(["24"]);
tag.dispatchEvent(new KeyboardEvent("keydown", { key: " " }));
tag.dispatchEvent(
new KeyboardEvent("keydown", {
key: " ",
bubbles: true,
cancelable: true,
})
);
expect(visiblePapers()).toEqual(["24", "23"]);
});

Expand Down
Loading