Improve website accessibility (#979)

* Improve website accessibility

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refine homepage search semantics

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Aaron Powell
2026-03-12 14:25:18 +11:00
committed by GitHub
parent eb7d223446
commit 423be2fc70
17 changed files with 293 additions and 160 deletions

View File

@@ -61,44 +61,46 @@ export function renderAgentsHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(item.path)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
${
item.model
? `<span class="resource-tag tag-model">${escapeHtml(
item.model
)}</span>`
: ""
}
${
item.tools
?.slice(0, 3)
.map(
(tool) =>
`<span class="resource-tag">${escapeHtml(tool)}</span>`
)
.join("") || ""
}
${
item.tools && item.tools.length > 3
? `<span class="resource-tag">+${
item.tools.length - 3
} more</span>`
: ""
}
${
item.hasHandoffs
? `<span class="resource-tag tag-handoffs">handoffs</span>`
: ""
}
${getLastUpdatedHtml(item.lastUpdated)}
<article class="resource-item" data-path="${escapeHtml(item.path)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
${
item.model
? `<span class="resource-tag tag-model">${escapeHtml(
item.model
)}</span>`
: ""
}
${
item.tools
?.slice(0, 3)
.map(
(tool) =>
`<span class="resource-tag">${escapeHtml(tool)}</span>`
)
.join("") || ""
}
${
item.tools && item.tools.length > 3
? `<span class="resource-tag">+${
item.tools.length - 3
} more</span>`
: ""
}
${
item.hasHandoffs
? `<span class="resource-tag tag-handoffs">handoffs</span>`
: ""
}
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
${getInstallDropdownHtml(resourceType, item.path, true)}
${getActionButtonsHtml(item.path, true)}
@@ -108,7 +110,7 @@ export function renderAgentsHtml(
GitHub
</a>
</div>
</div>
</article>
`;
})
.join("");

View File

@@ -59,41 +59,43 @@ export function renderHooksHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(
<article class="resource-item" data-path="${escapeHtml(
item.readmeFile
)}" data-hook-id="${escapeHtml(item.id)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
${item.hooks
.map(
(hook) =>
`<span class="resource-tag tag-hook">${escapeHtml(
hook
)}</span>`
)
.join("")}
${item.tags
.map(
(tag) =>
`<span class="resource-tag tag-tag">${escapeHtml(
tag
)}</span>`
)
.join("")}
${
item.assets.length > 0
? `<span class="resource-tag tag-assets">${
item.assets.length
} asset${item.assets.length === 1 ? "" : "s"}</span>`
: ""
}
${getLastUpdatedHtml(item.lastUpdated)}
)}" data-hook-id="${escapeHtml(item.id)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
${item.hooks
.map(
(hook) =>
`<span class="resource-tag tag-hook">${escapeHtml(
hook
)}</span>`
)
.join("")}
${item.tags
.map(
(tag) =>
`<span class="resource-tag tag-tag">${escapeHtml(
tag
)}</span>`
)
.join("")}
${
item.assets.length > 0
? `<span class="resource-tag tag-assets">${
item.assets.length
} asset${item.assets.length === 1 ? "" : "s"}</span>`
: ""
}
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
<button class="btn btn-primary download-hook-btn" data-hook-id="${escapeHtml(
item.id
@@ -108,7 +110,7 @@ export function renderHooksHtml(
item.path
)}" class="btn btn-secondary" target="_blank" onclick="event.stopPropagation()" title="View on GitHub">GitHub</a>
</div>
</div>
</article>
`;
})
.join("");

View File

@@ -55,43 +55,120 @@ export async function initHomepage(): Promise<void> {
const resultsDiv = document.getElementById('search-results');
if (searchInput && resultsDiv) {
const statusEl = document.getElementById("global-search-status");
const hideResults = (): void => {
resultsDiv.classList.add("hidden");
};
const showResults = (): void => {
resultsDiv.classList.remove("hidden");
};
const getResultButtons = (): HTMLButtonElement[] =>
Array.from(
resultsDiv.querySelectorAll<HTMLButtonElement>(".search-result")
);
const openResult = (resultEl: HTMLElement): void => {
const path = resultEl.dataset.path;
const type = resultEl.dataset.type;
if (path && type) {
hideResults();
openFileModal(path, type);
}
};
searchInput.addEventListener('input', debounce(() => {
const query = searchInput.value.trim();
if (query.length < 2) {
resultsDiv.classList.add('hidden');
resultsDiv.innerHTML = '';
if (statusEl) {
statusEl.textContent = '';
}
hideResults();
return;
}
const results = search.search(query).slice(0, 10);
if (results.length === 0) {
resultsDiv.innerHTML = '<div class="search-result-empty">No results found</div>';
if (statusEl) {
statusEl.textContent = 'No results found.';
}
} else {
resultsDiv.innerHTML = results.map(item => `
<div class="search-result" data-path="${escapeHtml(item.path)}" data-type="${escapeHtml(item.type)}">
<button type="button" class="search-result" data-path="${escapeHtml(item.path)}" data-type="${escapeHtml(item.type)}">
<span class="search-result-type">${getResourceIcon(item.type)}</span>
<div>
<div class="search-result-title">${search.highlight(item.title, query)}</div>
<div class="search-result-description">${truncate(item.description, 60)}</div>
</div>
</div>
</button>
`).join('');
// Add click handlers
resultsDiv.querySelectorAll('.search-result').forEach(el => {
if (statusEl) {
statusEl.textContent = `${results.length} result${results.length === 1 ? '' : 's'} available.`;
}
getResultButtons().forEach((el, index, buttons) => {
el.addEventListener('click', () => {
const path = (el as HTMLElement).dataset.path;
const type = (el as HTMLElement).dataset.type;
if (path && type) openFileModal(path, type);
openResult(el);
});
el.addEventListener("keydown", (event) => {
switch (event.key) {
case "ArrowDown":
event.preventDefault();
buttons[(index + 1) % buttons.length]?.focus();
break;
case "ArrowUp":
event.preventDefault();
if (index === 0) {
searchInput.focus();
} else {
buttons[index - 1]?.focus();
}
break;
case "Home":
event.preventDefault();
buttons[0]?.focus();
break;
case "End":
event.preventDefault();
buttons[buttons.length - 1]?.focus();
break;
case "Escape":
event.preventDefault();
hideResults();
searchInput.focus();
break;
}
});
});
}
resultsDiv.classList.remove('hidden');
showResults();
}, 200));
searchInput.addEventListener("keydown", (event) => {
if (event.key === "ArrowDown") {
const firstResult = getResultButtons()[0];
if (firstResult) {
event.preventDefault();
firstResult.focus();
}
}
if (event.key === "Escape") {
hideResults();
}
});
// Close results when clicking outside
document.addEventListener('click', (e) => {
if (!searchInput.contains(e.target as Node) && !resultsDiv.contains(e.target as Node)) {
resultsDiv.classList.add('hidden');
hideResults();
}
});
}

View File

@@ -61,17 +61,19 @@ export function renderInstructionsHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(item.path)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${applyToText ? `<span class="resource-tag">applies to: ${escapeHtml(applyToText)}</span>` : ''}
${item.extensions?.slice(0, 4).map((extension) => `<span class="resource-tag tag-extension">${escapeHtml(extension)}</span>`).join('') || ''}
${item.extensions && item.extensions.length > 4 ? `<span class="resource-tag">+${item.extensions.length - 4} more</span>` : ''}
${getLastUpdatedHtml(item.lastUpdated)}
<article class="resource-item" data-path="${escapeHtml(item.path)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${applyToText ? `<span class="resource-tag">applies to: ${escapeHtml(applyToText)}</span>` : ''}
${item.extensions?.slice(0, 4).map((extension) => `<span class="resource-tag tag-extension">${escapeHtml(extension)}</span>`).join('') || ''}
${item.extensions && item.extensions.length > 4 ? `<span class="resource-tag">+${item.extensions.length - 4} more</span>` : ''}
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
${getInstallDropdownHtml('instructions', item.path, true)}
${getActionButtonsHtml(item.path, true)}
@@ -79,7 +81,7 @@ export function renderInstructionsHtml(
GitHub
</a>
</div>
</div>
</article>
`;
})
.join('');

View File

@@ -70,21 +70,23 @@ export function renderPluginsHtml(
: escapeHtml(item.name);
return `
<div class="resource-item${isExternal ? ' resource-item-external' : ''}" data-path="${escapeHtml(item.path)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${metaTag}
${authorTag}
${item.tags?.slice(0, 4).map((tag) => `<span class="resource-tag">${escapeHtml(tag)}</span>`).join('') || ''}
${item.tags && item.tags.length > 4 ? `<span class="resource-tag">+${item.tags.length - 4} more</span>` : ''}
<article class="resource-item${isExternal ? ' resource-item-external' : ''}" data-path="${escapeHtml(item.path)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${metaTag}
${authorTag}
${item.tags?.slice(0, 4).map((tag) => `<span class="resource-tag">${escapeHtml(tag)}</span>`).join('') || ''}
${item.tags && item.tags.length > 4 ? `<span class="resource-tag">+${item.tags.length - 4} more</span>` : ''}
</div>
</div>
</div>
</button>
<div class="resource-actions">
<a href="${githubHref}" class="btn btn-secondary" target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()" title="${isExternal ? 'View repository' : 'View on GitHub'}">${isExternal ? 'Repository' : 'GitHub'}</a>
</div>
</div>
</article>
`;
})
.join('');

View File

@@ -65,31 +65,33 @@ export function renderSkillsHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(
<article class="resource-item" data-path="${escapeHtml(
item.skillFile
)}" data-skill-id="${escapeHtml(item.id)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
<span class="resource-tag tag-category">${escapeHtml(
item.category
)}</span>
${
item.hasAssets
? `<span class="resource-tag tag-assets">${
item.assetCount
} asset${item.assetCount === 1 ? "" : "s"}</span>`
: ""
}
<span class="resource-tag">${item.files.length} file${
item.files.length === 1 ? "" : "s"
}</span>
${getLastUpdatedHtml(item.lastUpdated)}
)}" data-skill-id="${escapeHtml(item.id)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
<span class="resource-tag tag-category">${escapeHtml(
item.category
)}</span>
${
item.hasAssets
? `<span class="resource-tag tag-assets">${
item.assetCount
} asset${item.assetCount === 1 ? "" : "s"}</span>`
: ""
}
<span class="resource-tag">${item.files.length} file${
item.files.length === 1 ? "" : "s"
}</span>
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
<button class="btn btn-primary download-skill-btn" data-skill-id="${escapeHtml(
item.id
@@ -104,7 +106,7 @@ export function renderSkillsHtml(
item.path
)}" class="btn btn-secondary" target="_blank" onclick="event.stopPropagation()" title="View on GitHub">GitHub</a>
</div>
</div>
</article>
`;
})
.join("");

View File

@@ -56,20 +56,22 @@ export function renderWorkflowsHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(item.path)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${item.triggers.map((trigger) => `<span class="resource-tag tag-trigger">${escapeHtml(trigger)}</span>`).join('')}
${getLastUpdatedHtml(item.lastUpdated)}
<article class="resource-item" data-path="${escapeHtml(item.path)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${item.triggers.map((trigger) => `<span class="resource-tag tag-trigger">${escapeHtml(trigger)}</span>`).join('')}
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
${getActionButtonsHtml(item.path)}
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary" target="_blank" onclick="event.stopPropagation()" title="View on GitHub">GitHub</a>
</div>
</div>
</article>
`;
})
.join('');