/* 简航 SimpleNav —— 前台交互:搜索引擎切换 + 联想下拉 + 合集标签页 */ (function () { 'use strict'; var cfg = window.SIMPLENAV || {}; /* ---------- 搜索引擎切换 ---------- */ var tabs = document.querySelectorAll('#engine-tabs .s-engine'); var form = document.getElementById('search-form'); var input = document.getElementById('search-text'); function activateEngine(el) { tabs.forEach(function (t) { t.classList.remove('active'); }); el.classList.add('active'); var search = el.getAttribute('data-search'); var param = el.getAttribute('data-param'); var slug = el.getAttribute('data-slug'); var ph = el.getAttribute('data-placeholder') || '搜索'; if (form) { form.setAttribute('action', search); form.setAttribute('data-slug', slug); } if (input) { input.setAttribute('name', param); input.setAttribute('placeholder', ph); } hideWord(); // 已经输了字就顺手换一份建议(auto 模式下各引擎来源不同) if (input && input.value.trim() !== '') { askSuggest(); } } tabs.forEach(function (el) { el.addEventListener('click', function () { activateEngine(el); input && input.focus(); }); }); /* ---------- 联想下拉 ---------- */ var word = document.getElementById('word'); var timer = null; var lastQ = null; var seq = 0; // 请求序号:防止先发的慢响应覆盖后发的快响应 var items = []; // 当前展示的建议词 var active = -1; // 选中项下标(键盘 ↓/↑ 与鼠标划过共用);-1 = 没选中 function hideWord() { if (word) { word.innerHTML = ''; word.style.display = 'none'; } items = []; active = -1; } function showWord(list) { if (!word) return; items = (list || []).slice(); active = -1; // 换了新建议就取消选中:此时回车应当用输入框原文 if (!items.length) { hideWord(); return; } word.innerHTML = items.map(function (t) { var d = document.createElement('li'); d.textContent = t; return d.outerHTML; }).join(''); word.style.display = 'block'; } /** 把 .active 标到当前选中项上(键盘与鼠标共用这一个状态) */ function paintWord() { if (!word) return; Array.prototype.forEach.call(word.children, function (li, i) { if (i === active) { li.classList.add('active'); } else { li.classList.remove('active'); } }); } function askSuggest() { if (!input) return; var q = input.value.trim(); if (q === '') { lastQ = null; hideWord(); return; } if (q === lastQ) { return; } lastQ = q; var mine = ++seq; var slug = form ? (form.getAttribute('data-slug') || '') : ''; fetch(cfg.suggest + '?slug=' + encodeURIComponent(slug) + '&q=' + encodeURIComponent(q)) .then(function (r) { return r.json(); }) .then(function (data) { if (mine === seq) { showWord(data && data.items); } }) .catch(function () { if (mine === seq) { hideWord(); } }); } if (input) { input.addEventListener('input', function () { clearTimeout(timer); active = -1; // 一开始打字就取消选中:之后回车应当用输入框原文 paintWord(); timer = setTimeout(askSuggest, 180); }); input.addEventListener('keydown', function (e) { var open = !!word && word.style.display === 'block' && items.length > 0; // ↓ / ↑:在下拉里移动选中项(只动高亮,不动输入框;回车时才落到输入框) if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { if (!open) { return; } e.preventDefault(); // 别让光标跳到文本首尾、也别让页面跟着滚 if (active < 0) { active = e.key === 'ArrowDown' ? 0 : items.length - 1; } else { active = (active + (e.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length; } paintWord(); return; } if (e.key === 'Escape') { hideWord(); return; } if (e.key === 'Enter') { // 只有**用 ↓/↑ 选中**了某条建议,回车才按建议词搜索。 // 没选中就一律用输入框里的原文 —— 老实现是「回车就套用第一条建议」, // 会把用户自己输入、而建议里没有的词悄悄换掉。 if (open && active >= 0) { input.value = items[active]; hideWord(); e.preventDefault(); if (form) { form.submit(); } } // active < 0:不拦截,交给浏览器默认的表单提交(即用输入框原文搜) return; } }); } /* 鼠标划过某条建议 → 同步高亮(与 ↓/↑ 共用 active,回车即可确认) */ document.addEventListener('mouseover', function (e) { if (!word || !e.target.closest) { return; } var hovered = e.target.closest('#word li'); if (!hovered) { return; } Array.prototype.forEach.call(word.children, function (li, i) { if (li === hovered) { active = i; } }); paintWord(); }); document.addEventListener('click', function (e) { if (word && !word.contains(e.target) && e.target !== input) { hideWord(); } var li = e.target.closest ? e.target.closest('#word li') : null; if (li && input) { input.value = li.textContent; hideWord(); if (form) { form.submit(); } } }); /* ---------- 合集标签页 ---------- */ var collTabs = document.querySelectorAll('#coll-tabs .tab'); collTabs.forEach(function (tab) { tab.addEventListener('click', function () { collTabs.forEach(function (t) { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); }); tab.classList.add('active'); tab.setAttribute('aria-selected', 'true'); document.querySelectorAll('.tab-panel').forEach(function (p) { p.classList.remove('active'); }); var panel = document.getElementById(tab.getAttribute('data-target')); if (panel) { panel.classList.add('active'); } }); }); /* ---------- 图标加载失败 → 退回首字母块(与 PHP 端配色一致) ---------- */ var PALETTE = ['#2F6BF7', '#5B8DEF', '#0EA5E9', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316']; function colorOf(s) { var h = 0; for (var i = 0; i < s.length; i++) { h = (h * 31 + s.charCodeAt(i)) | 0; } return PALETTE[Math.abs(h) % PALETTE.length]; } document.querySelectorAll('.mylist img.icon').forEach(function (img) { img.addEventListener('error', function () { var a = img.closest('a'); if (!a) { img.style.visibility = 'hidden'; return; } var nameEl = a.querySelector('.name'); var label = (nameEl ? nameEl.textContent : '?').trim() || '?'; var span = document.createElement('span'); span.className = 'ico-letter'; span.style.background = colorOf(label); span.textContent = label.charAt(0); img.parentNode.replaceChild(span, img); }, { once: true }); }); })();