guanghulab/channel-enhancements.js

319 lines
12 KiB
JavaScript
Raw Permalink Normal View History

// channel-enhancements.js - 频道搜索、面包屑、快捷键增强功能(修复版)
(function() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
function init() {
addStyles();
createSearchBox();
createBreadcrumb();
createShortcutHint();
initShortcuts();
initSearch();
window.addEventListener('hashchange', updateBreadcrumb);
updateBreadcrumb();
}
function addStyles() {
const style = document.createElement('style');
style.textContent = `
.channel-search-container {
padding: 16px 20px;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
display: flex;
align-items: center;
gap: 10px;
}
.channel-search-input {
flex: 1;
padding: 10px 16px;
border: 1px solid #ced4da;
border-radius: 24px;
font-size: 14px;
outline: none;
transition: border-color 0.2s;
}
.channel-search-input:focus {
border-color: #4d6bfe;
box-shadow: 0 0 0 3px rgba(77,107,254,0.1);
}
.channel-search-clear {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #6c757d;
padding: 0 8px;
display: none;
}
.channel-search-clear:hover {
color: #212529;
}
.channel-search-input:not(:placeholder-shown) + .channel-search-clear {
display: inline-block;
}
.highlight {
background-color: #ffeb3b;
padding: 2px 0;
border-radius: 2px;
}
.no-results {
text-align: center;
padding: 40px;
color: #6c757d;
font-style: italic;
}
.channel-breadcrumb {
padding: 12px 20px;
background: white;
border-bottom: 1px solid #e9ecef;
font-size: 14px;
}
.channel-breadcrumb a {
color: #4d6bfe;
text-decoration: none;
}
.channel-breadcrumb a:hover {
text-decoration: underline;
}
.channel-breadcrumb span {
color: #6c757d;
}
.channel-breadcrumb .current {
color: #212529;
font-weight: 500;
}
.shortcut-hint {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(0,0,0,0.7);
color: white;
padding: 8px 16px;
border-radius: 30px;
font-size: 12px;
backdrop-filter: blur(4px);
z-index: 1000;
}
.shortcut-hint kbd {
background: rgba(255,255,255,0.2);
padding: 2px 6px;
border-radius: 4px;
margin: 0 2px;
}
.module-card.selected {
outline: 2px solid #4d6bfe;
outline-offset: 2px;
transform: scale(1.02);
transition: all 0.2s;
}
`;
document.head.appendChild(style);
}
function createSearchBox() {
const container = document.querySelector('.module-grid') || document.querySelector('#module-list') || document.querySelector('.channel-content');
if (!container) return;
const searchContainer = document.createElement('div');
searchContainer.className = 'channel-search-container';
searchContainer.innerHTML = `
<input type="text" class="channel-search-input" placeholder="搜索模块...">
<button class="channel-search-clear">&times;</button>
`;
container.parentNode.insertBefore(searchContainer, container);
const searchInput = searchContainer.querySelector('.channel-search-input');
const clearBtn = searchContainer.querySelector('.channel-search-clear');
searchInput.addEventListener('input', function() {
filterModules(this.value);
});
clearBtn.addEventListener('click', function() {
searchInput.value = '';
filterModules('');
searchInput.focus();
});
window.__channelSearchInput = searchInput;
}
function filterModules(keyword) {
const cards = document.querySelectorAll('.module-card');
const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
let hasResults = false;
// 先移除所有高亮
removeAllHighlights();
cards.forEach(card => {
const text = card.innerText || card.textContent;
if (keyword === '') {
card.style.display = '';
hasResults = true;
} else {
const lowerText = text.toLowerCase();
const lowerKeyword = keyword.toLowerCase();
if (lowerText.includes(lowerKeyword)) {
card.style.display = '';
highlightText(card, keyword);
hasResults = true;
} else {
card.style.display = 'none';
}
}
});
let noResultsEl = document.querySelector('.no-results');
if (!hasResults && keyword !== '') {
if (!noResultsEl) {
noResultsEl = document.createElement('div');
noResultsEl.className = 'no-results';
noResultsEl.textContent = '没有找到匹配的模块';
container.parentNode.insertBefore(noResultsEl, container.nextSibling);
}
} else {
if (noResultsEl) noResultsEl.remove();
}
}
// 移除所有高亮span恢复原文本
function removeAllHighlights() {
document.querySelectorAll('.highlight').forEach(span => {
const parent = span.parentNode;
parent.replaceChild(document.createTextNode(span.textContent), span);
parent.normalize(); // 合并相邻文本节点
});
}
// 高亮匹配文本(不破坏事件监听)
function highlightText(card, keyword) {
const regex = new RegExp(`(${keyword})`, 'gi');
const walk = document.createTreeWalker(card, NodeFilter.SHOW_TEXT, {
acceptNode: function(node) {
// 跳过已经高亮过的span内部
if (node.parentNode.classList && node.parentNode.classList.contains('highlight')) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
}
}, false);
const textNodes = [];
while (walk.nextNode()) textNodes.push(walk.currentNode);
textNodes.forEach(node => {
const text = node.nodeValue;
if (regex.test(text)) {
const span = document.createElement('span');
span.className = 'highlight';
span.innerHTML = text.replace(regex, '<span class="highlight">$1</span>');
node.parentNode.replaceChild(span, node);
}
});
}
function createBreadcrumb() {
const searchContainer = document.querySelector('.channel-search-container');
const breadcrumb = document.createElement('div');
breadcrumb.className = 'channel-breadcrumb';
breadcrumb.id = 'channelBreadcrumb';
if (searchContainer) {
searchContainer.parentNode.insertBefore(breadcrumb, searchContainer.nextSibling);
} else {
const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
if (container) {
container.parentNode.insertBefore(breadcrumb, container);
}
}
}
function updateBreadcrumb() {
const breadcrumb = document.getElementById('channelBreadcrumb');
if (!breadcrumb) return;
const hash = window.location.hash.slice(1) || '';
let moduleName = '频道';
if (hash.startsWith('module-')) {
const moduleId = hash.replace('module-', '');
const moduleNames = {
'M06': '工单管理',
'M08': '数据看板',
'M11': '用户反馈'
};
moduleName = moduleNames[moduleId] || moduleId;
}
breadcrumb.innerHTML = `
<a href="#/">首页</a> &gt;
<a href="#/channel">频道</a> &gt;
<span class="current">${moduleName}</span>
`;
}
function createShortcutHint() {
const hint = document.createElement('div');
hint.className = 'shortcut-hint';
hint.innerHTML = `
<kbd>K</kbd> ·
<kbd></kbd><kbd></kbd> ·
<kbd>Enter</kbd> ·
<kbd>Esc</kbd>
`;
document.body.appendChild(hint);
}
function initShortcuts() {
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
const searchInput = window.__channelSearchInput;
if (searchInput) searchInput.focus();
}
if (e.key === 'Escape') {
const searchInput = window.__channelSearchInput;
if (searchInput && document.activeElement === searchInput) {
searchInput.value = '';
filterModules('');
searchInput.blur();
} else if (searchInput && searchInput.value) {
searchInput.value = '';
filterModules('');
}
}
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
const cards = Array.from(document.querySelectorAll('.module-card:not([style*="display: none"])'));
if (cards.length === 0) return;
e.preventDefault();
let selectedIndex = cards.findIndex(card => card.classList.contains('selected'));
if (selectedIndex === -1) {
selectedIndex = e.key === 'ArrowDown' ? 0 : cards.length - 1;
} else {
cards[selectedIndex].classList.remove('selected');
if (e.key === 'ArrowDown') {
selectedIndex = (selectedIndex + 1) % cards.length;
} else {
selectedIndex = (selectedIndex - 1 + cards.length) % cards.length;
}
}
cards[selectedIndex].classList.add('selected');
cards[selectedIndex].scrollIntoView({ block: 'nearest' });
}
if (e.key === 'Enter') {
const selected = document.querySelector('.module-card.selected');
if (selected) {
// 触发点击事件,模拟鼠标点击
selected.click();
}
}
});
}
function initSearch() {
// 不需要预先保存innerHTML了
}
})();