$(document).ready(function(){initTheme();initSearch();initBookmarks();initScrollEffects();initCategoryNav();});
/**
* Initialize theme functionality
*/
function initTheme() {
// Initialize tooltips
$('[data-toggle="tooltip"]').tooltip();
// Handle mobile menu
$('.navbar-toggler').on('click', function() {
$('body').toggleClass('menu-open');
});
}
/**
* Initialize search functionality
*/
function initSearch() {
const $input = $('#searchInput');
const $suggest = $('#searchSuggestions');
const $clear = $('#clearSearch');
let timer;
// Search input handler
$input.on('input', function() {
const q = this.value.toLowerCase().trim();
clearTimeout(timer);
timer = setTimeout(() => performSearch(q), 220);
if (!q) {
$clear.hide();
} else {
$clear.show();
}
});
// Clear search on escape
$input.on('keydown', e => {
if (e.key === 'Escape') {
clearSearch();
}
});
// Clear button click
$clear.on('click', () => {
clearSearch();
$input.focus();
});
// Hide suggestions when clicking outside
$(document).on('click', function(e) {
if (!$(e.target).closest('.search-container').length) {
$suggest.hide();
}
});
// Focus search with Ctrl+K or Cmd+K
$(document).on('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
$input.focus();
}
});
}
/**
* Perform search and show results
*/
function performSearch(query) {
const searchSuggestions = $('#searchSuggestions');
if (!query) {
clearSearch();
return;
}
const bookmarks = $('.bookmark-card').not('.more-card');
const matches = [];
bookmarks.each(function() {
const $card = $(this);
const title = $card.data('title') || '';
const url = $card.data('url') || '';
const description = $card.data('description') || '';
const searchText = (title + ' ' + url + ' ' + description).toLowerCase();
if (searchText.includes(query)) {
matches.push({
element: $card,
title: title,
url: url,
description: description,
score: calculateSearchScore(query, title, url, description)
});
}
});
matches.sort((a, b) => b.score - a.score);
displaySearchResults(matches, query); // 仅展示建议,不再过滤主列表
}
/**
* Calculate search relevance score
*/
function calculateSearchScore(query, title, url, description) {
let score = 0;
const lowerQuery = query.toLowerCase();
const lowerTitle = title.toLowerCase();
const lowerUrl = url.toLowerCase();
const lowerDesc = description.toLowerCase();
// Title matches are most important
if (lowerTitle.includes(lowerQuery)) {
score += 10;
if (lowerTitle.startsWith(lowerQuery)) score += 5;
}
// URL matches
if (lowerUrl.includes(lowerQuery)) {
score += 5;
}
// Description matches
if (lowerDesc.includes(lowerQuery)) {
score += 2;
}
return score;
}
/**
* Display search suggestions
*/
function displaySearchResults(matches, query) {
const searchSuggestions = $('#searchSuggestions');
if (matches.length === 0) {
searchSuggestions.html('
没有找到匹配的书签
').show();
return;
}
let html = '';
matches.slice(0, 5).forEach(match => { // 限制最多 5 条
const highlightedTitle = highlightText(match.title, query);
const domain = extractDomain(match.url);
html += `
`;
});
searchSuggestions.html(html).show();
$('.search-suggestion-item').off('click').on('click', function() {
const url = $(this).data('url');
if (url) window.open(url, '_blank');
});
}
/**
* Highlight text matches
*/
function highlightText(text, query) {
if (!query) return text;
const regex = new RegExp(`(${escapeRegex(query)})`, 'gi');
return text.replace(regex, '$1');
}
/**
* Escape regex special characters
*/
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Extract domain from URL
*/
function extractDomain(url) {
try {
return new URL(url).hostname;
} catch {
return url;
}
}
/**
* Clear search results
*/
function clearSearch() {
$('#searchInput').val('');
$('#searchSuggestions').hide().empty();
}
/**
* Update category counts
*/
function updateCategoryCounts() {
$('.category-section').each(function() {
const $section = $(this);
const visibleCount = $section.find('.bookmark-card:not(.d-none):not(.more-card)').length;
$section.find('.badge').text(visibleCount);
});
}
/**
* Initialize bookmark interactions
*/
function initBookmarks() {
// Copy link functionality
$(document).on('click', '.copy-link', function(e) {
e.preventDefault();
e.stopPropagation();
const url = $(this).data('url');
copyToClipboard(url);
showToast('链接已复制', 'success');
});
// QR code functionality
$(document).on('click', '.qr-code', function(e) {
e.preventDefault();
e.stopPropagation();
const url = $(this).data('url');
showQRCode(url);
});
// Add link functionality (for logged-in users)
$('#addLinkBtn').on('click', function() {
window.open('/index.php?c=admin&page=add_link', '_blank');
});
}
/**
* Copy text to clipboard
*/
function copyToClipboard(text) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text);
} else {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand('copy');
textArea.remove();
}
}
/**
* Show QR code modal
*/
function showQRCode(url) {
$('#qrcode').empty();
$('#qrUrl').text(url);
try {
// 使用 qrcode.min.js 库(QRCode 构造函数)
const qrobj = new QRCode('qrcode', {
text: url,
width: 180,
height: 180,
colorDark: '#1f2937',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.H
});
} catch (e) {
$('#qrcode').html('二维码生成失败
');
}
$('#qrModal').modal('show');
}
/**
* Show simple toast notification
*/
function showToast(message, type = 'info') {
const toastId = 'toast-' + Date.now();
const alertClass = type === 'success' ? 'alert-success' :
type === 'error' ? 'alert-danger' : 'alert-info';
const toast = $(`
${message}
`);
$('body').append(toast);
// Auto remove after 2 seconds
setTimeout(() => {
$(`#${toastId}`).alert('close');
}, 2000);
}
/**
* Initialize scroll effects
*/
function initScrollEffects() {
const $backToTop = $('#backToTop');
// Back to top button
$(window).on('scroll', function() {
if ($(this).scrollTop() > 300) {
$backToTop.addClass('show');
} else {
$backToTop.removeClass('show');
}
});
// Back to top click
$backToTop.on('click', function() {
$('html, body').animate({
scrollTop: 0
}, 500);
});
}
/**
* Initialize category navigation
*/
function initCategoryNav() {
const $tabs = $('#categoryTabs .nav-link');
const $scroll = $('#categoryScroll');
// Handle category tab clicks
$tabs.on('click', function(e) {
e.preventDefault();
const $t = $(this);
const cat = $t.data('category');
// Update active tab
$tabs.removeClass('active');
$t.addClass('active');
// Filter categories
if (cat === 'all') {
$('.category-section').show();
} else {
$('.category-section').hide();
$('#category-' + cat).show();
}
// 计算当前tab在容器中的位置,自动滚动使其尽量居中
const tabEl = this;
const container = $scroll.get(0);
const cRect = container.getBoundingClientRect();
const tRect = tabEl.getBoundingClientRect();
const offset = (tRect.left - cRect.left) - (cRect.width / 2 - tRect.width / 2);
container.scrollBy({ left: offset, behavior: 'smooth' });
});
}
/**
* 删除 initCategoryNavArrows 相关调用后保留函数引用以防外部依赖(可选保留空)
*/
function initCategoryNavArrows(){}
// 重新初始化分类箭头(防止某些情况下未绑定)
$(function(){
if(typeof initCategoryNavArrows==='function'){
initCategoryNavArrows();
}
});
/**
* Initialize scrollable category tabs
*/
function initScrollableCategoryTabs(){
var $wrap = $('#catTabsScrollWrap');
if(!$wrap.length) return;
var $inner = $('#catScrollInner');
var $prev = $('#catScrollPrev');
var $next = $('#catScrollNext');
function maxScroll(){return $inner[0].scrollWidth - $inner[0].clientWidth;}
function update(){var sl=$inner.scrollLeft();var max=maxScroll();$prev.prop('disabled',sl<=2);$next.prop('disabled',sl>=max-2||max<=0);if(max<=0){$prev.hide();$next.hide();}else{$prev.show();$next.show();}}
function smoothTo(target){var start=$inner.scrollLeft();var max=maxScroll();target=Math.max(0,Math.min(target,max));if(Math.abs(target-start)<2){$inner.scrollLeft(target);update();return;}var dur=360;var t0=null;function step(ts){if(!t0)t0=ts;var p=Math.min(1,(ts-t0)/dur);var ease=1-Math.pow(1-p,3);var cur=start+(target-start)*ease;$inner.scrollLeft(cur);if(p<1)requestAnimationFrame(step);else update();}requestAnimationFrame(step);}
function stepSize(){return Math.max(120,Math.round($inner.width()*0.55));}
$prev.on('click',function(){smoothTo($inner.scrollLeft()-stepSize());});
$next.on('click',function(){smoothTo($inner.scrollLeft()+stepSize());});
$inner.on('scroll',update);
$(window).on('resize',update);
update();
// 居中当前激活项(首次)
var $active=$inner.find('.nav-link.active');
if($active.length){var left=$active.position().left;var target=left-($inner.width()-$active.outerWidth())/2;smoothTo(target);}
// 点击自动居中
$inner.on('click','.nav-link',function(){var left=$(this).position().left;var target=left-($inner.width()-$(this).outerWidth())/2;smoothTo(target);});
}
$(document).ready(function(){initScrollableCategoryTabs();});
/**
* Handle responsive layout changes
*/
function handleResponsiveLayout() {
// Any responsive adjustments can be added here
// Currently handled by CSS Grid
}
// Handle window resize
$(window).on('resize', handleResponsiveLayout);
// Initialize responsive layout on load
handleResponsiveLayout();