skill-catalog/skills/skill-catalog/assets/app.js
2026-10-02 14:26:54 +08:00

152 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 目录站交互:搜索、按层/依赖筛选、复制命令。无依赖、无构建。
(() => {
const search = document.getElementById('search');
const count = document.getElementById('count');
const empty = document.getElementById('empty');
const sections = [...document.querySelectorAll('.layer-section')];
const cards = [...document.querySelectorAll('.card')];
const reqFilters = [...document.querySelectorAll('.chip[data-requires]')];
const platformFilters = [...document.querySelectorAll('.chip[data-platform]')];
const layerFilters = [...document.querySelectorAll('.seg[data-layer-filter]')];
// 主题切换:写进 <html data-theme> 并记住;引导脚本已在首帧前设好初值。
const toggle = document.getElementById('theme-toggle');
toggle?.addEventListener('click', () => {
const root = document.documentElement;
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
root.dataset.theme = next;
try {
localStorage.setItem('pi-hub-theme', next);
} catch {
/* 隐私模式下存不了就只在本次会话生效 */
}
});
// 键盘:/ 聚焦搜索,Esc 清空并退出;筛选状态写进 URL(可分享/可回退)。
document.addEventListener('keydown', event => {
const typing = /^(INPUT|TEXTAREA|SELECT)$/.test(document.activeElement?.tagName ?? '');
if (event.key === '/' && !typing) {
event.preventDefault();
search?.focus();
search?.select();
} else if (event.key === 'Escape' && (typing || search?.value)) {
if (search) {
search.value = '';
apply();
if (typing) search.blur();
}
}
});
const syncHash = () => {
const hash = activeLayers.size ? `#${[...activeLayers].sort().join(',')}` : '';
if (location.hash !== hash) history.replaceState(null, '', `${location.pathname}${location.search}${hash}`);
};
const readHash = () => {
const wanted = location.hash.replace(/^#/, '').split(',').map(s => s.trim()).filter(Boolean);
for (const chip of platformFilters) {
chip.addEventListener('click', () => {
const value = chip.dataset.platform;
if (activePlatforms.has(value)) activePlatforms.delete(value);
else activePlatforms.add(value);
chip.classList.toggle('active', activePlatforms.has(value));
chip.setAttribute('aria-pressed', String(activePlatforms.has(value)));
apply();
});
}
for (const chip of layerFilters) {
const layer = chip.dataset.layerFilter;
if (wanted.includes(layer)) {
activeLayers.add(layer);
chip.classList.add('active');
chip.setAttribute('aria-pressed', 'true');
}
}
};
for (const chip of layerFilters) {
chip.addEventListener('click', () => {
chip.setAttribute('aria-pressed', String(activeLayers.has(chip.dataset.layerFilter)));
syncHash();
});
}
window.addEventListener('hashchange', () => location.reload());
readHash();
if (activeLayers.size) apply();
if (!cards.length) return;
const activeRequires = new Set();
const activeLayers = new Set();
const activePlatforms = new Set();
const apply = () => {
const query = (search?.value ?? '').trim().toLowerCase();
let shown = 0;
for (const card of cards) {
const haystack = (card.dataset.search ?? '').toLowerCase();
const matchQuery = !query || haystack.includes(query);
const matchRequires = activeRequires.size === 0 || [...activeRequires].every(name => haystack.includes(name.toLowerCase()));
const matchLayer = activeLayers.size === 0 || activeLayers.has(card.dataset.layer);
const cardPlatforms = (card.dataset.platforms ?? '').split(' ').filter(Boolean);
const matchPlatform = activePlatforms.size === 0 || [...activePlatforms].some(value => cardPlatforms.includes(value));
const visible = matchQuery && matchRequires && matchLayer && matchPlatform;
card.hidden = !visible;
if (visible) shown += 1;
}
for (const section of sections) {
section.hidden = ![...section.querySelectorAll('.card')].some(card => !card.hidden);
}
if (count) count.textContent = String(shown);
if (empty) empty.hidden = shown !== 0;
const clear = document.getElementById('clear-filters');
if (clear) clear.hidden = activeLayers.size + activeRequires.size + activePlatforms.size === 0;
};
search?.addEventListener('input', apply);
for (const chip of reqFilters) {
chip.addEventListener('click', () => {
const name = chip.dataset.requires;
if (activeRequires.has(name)) activeRequires.delete(name);
else activeRequires.add(name);
chip.classList.toggle('active', activeRequires.has(name));
apply();
});
}
for (const chip of layerFilters) {
chip.addEventListener('click', () => {
const layer = chip.dataset.layerFilter;
if (activeLayers.has(layer)) activeLayers.delete(layer);
else activeLayers.add(layer);
chip.classList.toggle('active', activeLayers.has(layer));
apply();
});
}
for (const button of document.querySelectorAll('.copy')) {
const label = button.textContent;
button.addEventListener('click', async () => {
const text = button.dataset.copy ?? '';
try {
await navigator.clipboard.writeText(text);
button.textContent = '已复制';
} catch {
button.textContent = '按 ⌘C';
}
setTimeout(() => (button.textContent = label), 1500);
});
}
document.getElementById('clear-filters')?.addEventListener('click', () => {
activeLayers.clear();
activeRequires.clear();
activePlatforms.clear();
for (const chip of [...layerFilters, ...reqFilters, ...platformFilters]) {
chip.classList.remove('active');
chip.setAttribute('aria-pressed', 'false');
}
if (search) search.value = '';
history.replaceState(null, '', location.pathname);
apply();
});
apply();
})();