455 lines
18 KiB
JavaScript
455 lines
18 KiB
JavaScript
window.initSearch = function() {
|
|
initSearchModal();
|
|
initInlineSearch();
|
|
};
|
|
|
|
var searchLoadingPromise = null;
|
|
|
|
function debounce(fn, delay) {
|
|
var timer;
|
|
return function() {
|
|
var context = this;
|
|
var args = arguments;
|
|
clearTimeout(timer);
|
|
timer = setTimeout(function() { fn.apply(context, args); }, delay);
|
|
};
|
|
}
|
|
|
|
function loadSearchIndex() {
|
|
if (window.searchIndex) {
|
|
return Promise.resolve(window.searchIndex);
|
|
}
|
|
|
|
if (searchLoadingPromise) {
|
|
return searchLoadingPromise;
|
|
}
|
|
|
|
window.isSearchingLoading = true;
|
|
searchLoadingPromise = fetch('/index.json')
|
|
.then(response => {
|
|
if (!response.ok) throw new Error(response.statusText);
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
window.searchIndex = data;
|
|
window.isSearchingLoading = false;
|
|
searchLoadingPromise = null;
|
|
return data;
|
|
})
|
|
.catch(err => {
|
|
console.error('Failed to load search index:', err);
|
|
window.isSearchingLoading = false;
|
|
searchLoadingPromise = null;
|
|
return null; // Return null on error to handle gracefully
|
|
});
|
|
|
|
return searchLoadingPromise;
|
|
}
|
|
|
|
function initSearchModal() {
|
|
var searchBtn = document.getElementById('search-btn');
|
|
var searchModal = document.getElementById('search-modal');
|
|
|
|
if (searchBtn && searchModal) {
|
|
// Clone to remove old listeners
|
|
var newSearchBtn = searchBtn.cloneNode(true);
|
|
searchBtn.parentNode.replaceChild(newSearchBtn, searchBtn);
|
|
searchBtn = newSearchBtn;
|
|
|
|
searchBtn.addEventListener('click', function() {
|
|
searchModal.classList.add('show');
|
|
var input = document.getElementById('search-input');
|
|
if (input) setTimeout(function() { input.focus(); }, 100);
|
|
|
|
loadSearchIndex();
|
|
});
|
|
|
|
// Close logic
|
|
var closeBtn = searchModal.querySelector('.close-search');
|
|
if (closeBtn) {
|
|
closeBtn.onclick = function() {
|
|
searchModal.classList.remove('show');
|
|
};
|
|
}
|
|
|
|
// Click outside to close
|
|
searchModal.onclick = function(e) {
|
|
if (e.target === searchModal) {
|
|
searchModal.classList.remove('show');
|
|
}
|
|
};
|
|
|
|
// Search Input Logic
|
|
var searchInput = document.getElementById('search-input');
|
|
var searchResults = document.getElementById('search-results');
|
|
|
|
if (searchInput && searchResults) {
|
|
// Clone input to remove old listeners
|
|
var newInput = searchInput.cloneNode(true);
|
|
searchInput.parentNode.replaceChild(newInput, searchInput);
|
|
searchInput = newInput;
|
|
|
|
// Clone results to remove old listeners
|
|
var newResults = searchResults.cloneNode(true);
|
|
searchResults.parentNode.replaceChild(newResults, searchResults);
|
|
searchResults = newResults;
|
|
|
|
// Result click — use data-href to avoid pjax anchor conflicts
|
|
searchResults.addEventListener('click', function(e) {
|
|
var item = e.target.closest('.search-result-item');
|
|
if (!item) return;
|
|
var href = item.getAttribute('data-href');
|
|
if (!href) return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
searchModal.classList.remove('show');
|
|
searchModal.style.transition = 'none';
|
|
requestAnimationFrame(function() {
|
|
requestAnimationFrame(function() {
|
|
searchModal.style.transition = '';
|
|
});
|
|
});
|
|
if (window.pjax) {
|
|
window.pjax.loadUrl(href, { triggerElement: item });
|
|
} else {
|
|
window.location.href = href;
|
|
}
|
|
});
|
|
|
|
searchInput.addEventListener('input', debounce(function() {
|
|
var query = this.value.trim().toLowerCase();
|
|
|
|
if (query.length === 0) {
|
|
searchResults.innerHTML = '<p class="search-tip">输入关键词并回车进行搜索</p>';
|
|
return;
|
|
}
|
|
|
|
if (!window.searchIndex) {
|
|
searchResults.innerHTML = '<p class="search-tip">索引加载中...</p>';
|
|
loadSearchIndex().then(function(data) {
|
|
if (data && searchInput.value.trim().length > 0) {
|
|
searchInput.dispatchEvent(new Event('input'));
|
|
} else if (!data) {
|
|
searchResults.innerHTML = '<p class="search-tip">索引加载失败</p>';
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
var results = window.searchIndex.filter(function(item) {
|
|
return (item.title && item.title.toLowerCase().includes(query)) ||
|
|
(item.summary && item.summary.toLowerCase().includes(query));
|
|
});
|
|
|
|
if (results.length > 0) {
|
|
var html = results.map(function(item) {
|
|
var path = item.permalink.replace(/^https?:\/\/[^\/]+/, '');
|
|
return '<div class="search-result-item" data-href="' + path + '" role="link" tabindex="0">' +
|
|
'<span class="search-result-title">' + item.title + '</span>' +
|
|
'<span class="search-result-date">' + item.date + '</span>' +
|
|
'<div class="search-result-summary">' + (item.summary || '') + '</div>' +
|
|
'</div>';
|
|
}).join('');
|
|
searchResults.innerHTML = html;
|
|
} else {
|
|
searchResults.innerHTML = '<p class="search-tip">未找到相关文章</p>';
|
|
}
|
|
}), 200);
|
|
}
|
|
}
|
|
}
|
|
|
|
function initInlineSearch() {
|
|
var header = document.getElementById('essay-header');
|
|
var searchContainer = document.getElementById('search-container');
|
|
var input = document.getElementById('inline-search-input');
|
|
var resultsContainer = document.getElementById('inline-search-results');
|
|
|
|
window.toggleSearchMode = function(show) {
|
|
if (!header || !searchContainer || !input || !resultsContainer) {
|
|
console.warn('Search elements missing, cannot toggle mode.');
|
|
return;
|
|
}
|
|
|
|
var searchHint = document.querySelector('.search-hint-wrapper');
|
|
|
|
if (show) {
|
|
if (searchHint) searchHint.style.display = 'none'; // Hide hint completely to avoid layout shifts
|
|
header.style.display = 'none';
|
|
searchContainer.style.display = 'block';
|
|
input.focus();
|
|
loadSearchIndex().then(() => {
|
|
console.log('Search index loaded via toggle');
|
|
}).catch(e => console.error('Search index load failed', e));
|
|
// Add Esc key listener
|
|
document.addEventListener('keydown', escListener);
|
|
} else {
|
|
if (searchHint) searchHint.style.display = ''; // Restore hint
|
|
header.style.display = 'inline-flex';
|
|
searchContainer.style.display = 'none';
|
|
input.value = '';
|
|
// Remove .has-value class
|
|
searchContainer.querySelector('.search-input-group').classList.remove('has-value');
|
|
resultsContainer.classList.remove('show');
|
|
resultsContainer.innerHTML = '';
|
|
// Remove Esc key listener
|
|
document.removeEventListener('keydown', escListener);
|
|
}
|
|
};
|
|
|
|
var escListener = function(e) {
|
|
if (e.key === 'Escape') {
|
|
window.toggleSearchMode(false);
|
|
}
|
|
};
|
|
|
|
if (!header || !searchContainer || !input || !resultsContainer) return;
|
|
|
|
// Input Event
|
|
var newInput = input.cloneNode(true);
|
|
input.parentNode.replaceChild(newInput, input);
|
|
input = newInput;
|
|
|
|
// Handle Search Icon Click (Focus input)
|
|
var searchIcon = searchContainer.querySelector('.search-icon');
|
|
if (searchIcon) {
|
|
searchIcon.addEventListener('click', function() {
|
|
input.focus();
|
|
});
|
|
}
|
|
|
|
// Handle Close Icon Logic
|
|
var closeIcon = searchContainer.querySelector('.close-icon');
|
|
if (closeIcon) {
|
|
// Remove inline onclick handler if present
|
|
closeIcon.onclick = null;
|
|
closeIcon.removeAttribute('onclick');
|
|
|
|
// Add new custom handler
|
|
closeIcon.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (input.value.trim().length > 0) {
|
|
// Clear input
|
|
input.value = '';
|
|
input.focus();
|
|
// Trigger input event to update UI
|
|
input.dispatchEvent(new Event('input'));
|
|
} else {
|
|
// Close search
|
|
window.toggleSearchMode(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
input.addEventListener('input', debounce(function() {
|
|
var query = this.value.trim().toLowerCase();
|
|
var inputGroup = this.closest('.search-input-group');
|
|
|
|
// Toggle has-value class
|
|
if (this.value.length > 0) {
|
|
inputGroup.classList.add('has-value');
|
|
} else {
|
|
inputGroup.classList.remove('has-value');
|
|
}
|
|
|
|
if (query.length === 0) {
|
|
resultsContainer.classList.remove('show');
|
|
resultsContainer.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
if (!window.searchIndex) {
|
|
resultsContainer.innerHTML = '<div class="search-result-item no-result">索引加载中...</div>';
|
|
resultsContainer.classList.add('show');
|
|
|
|
loadSearchIndex().then(function(data) {
|
|
// If data loaded successfully and user hasn't cleared input
|
|
if (data && input.value.trim().length > 0) {
|
|
input.dispatchEvent(new Event('input'));
|
|
} else if (!data) {
|
|
resultsContainer.innerHTML = '<div class="search-result-item no-result">索引加载失败</div>';
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
var results = window.searchIndex.filter(function(item) {
|
|
return (item.title && item.title.toLowerCase().includes(query)) ||
|
|
(item.summary && item.summary.toLowerCase().includes(query));
|
|
});
|
|
|
|
if (results.length > 0) {
|
|
var html = results.slice(0, 5).map(function(item) {
|
|
var path = item.permalink.replace(/^https?:\/\/[^\/]+/, '');
|
|
return '<div class="search-result-item" data-href="' + path + '" role="link" tabindex="0">' +
|
|
'<span class="search-result-title">' + item.title + '</span>' +
|
|
'<div class="search-result-meta">' +
|
|
'<span class="search-result-date">' + item.date + '</span>' +
|
|
'<span class="search-result-summary-text">' + (item.summary || '') + '</span>' +
|
|
'</div>' +
|
|
'</div>';
|
|
}).join('');
|
|
|
|
resultsContainer.innerHTML = html;
|
|
resultsContainer.classList.add('show');
|
|
} else {
|
|
resultsContainer.innerHTML = '<div class="search-result-item no-result">暂无相关内容</div>';
|
|
resultsContainer.classList.add('show');
|
|
}
|
|
}), 200);
|
|
|
|
// Close on click outside
|
|
if (!window.inlineSearchClickOutside) {
|
|
window.inlineSearchClickOutside = function(e) {
|
|
var searchContainer = document.getElementById('search-container');
|
|
if (!searchContainer || searchContainer.style.display !== 'block') return;
|
|
var header = document.getElementById('essay-header');
|
|
var resultsContainer = document.getElementById('inline-search-results');
|
|
|
|
if (header && resultsContainer &&
|
|
!searchContainer.contains(e.target) &&
|
|
!header.contains(e.target) &&
|
|
!resultsContainer.contains(e.target)) {
|
|
window.toggleSearchMode(false);
|
|
}
|
|
};
|
|
document.addEventListener('click', window.inlineSearchClickOutside);
|
|
}
|
|
|
|
// Click delegation for results
|
|
resultsContainer.addEventListener('click', function(e) {
|
|
var item = e.target.closest('.search-result-item');
|
|
if (!item) return;
|
|
var href = item.getAttribute('data-href');
|
|
if (!href) return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
window.toggleSearchMode(false);
|
|
if (window.pjax) {
|
|
window.pjax.loadUrl(href, { triggerElement: item });
|
|
} else {
|
|
window.location.href = href;
|
|
}
|
|
});
|
|
|
|
// Search Hint Typewriter Logic
|
|
var header = document.getElementById('essay-header');
|
|
var searchHintWrapper = header ? header.querySelector('.search-hint-wrapper') : null;
|
|
var searchHintText = searchHintWrapper ? searchHintWrapper.querySelector('.search-hint') : null;
|
|
|
|
if (searchHintWrapper && searchHintText) {
|
|
// --- Smart Greeting Logic ---
|
|
var text = window.searchHintText || "点击随笔 开始搜索";
|
|
var now = new Date();
|
|
var month = now.getMonth() + 1;
|
|
var day = now.getDate();
|
|
var dateStr = (month < 10 ? '0' : '') + month + '-' + (day < 10 ? '0' : '') + day;
|
|
var hour = now.getHours();
|
|
|
|
// 1. Check Holidays
|
|
if (window.searchHolidays && window.searchHolidays[dateStr]) {
|
|
text = window.searchHolidays[dateStr];
|
|
}
|
|
// 2. Check Time of Day (if greetings configured)
|
|
else if (window.searchGreetings) {
|
|
function pickGreeting(key) {
|
|
var val = window.searchGreetings[key];
|
|
if (!val) return null;
|
|
if (Array.isArray(val)) return val[Math.floor(Math.random() * val.length)];
|
|
return val;
|
|
}
|
|
var picked = null;
|
|
if (hour >= 6 && hour < 11) picked = pickGreeting('morning');
|
|
else if (hour >= 11 && hour < 14) picked = pickGreeting('noon');
|
|
else if (hour >= 14 && hour < 18) picked = pickGreeting('afternoon');
|
|
else if (hour >= 18 && hour < 22) picked = pickGreeting('evening');
|
|
else if (hour >= 22 || hour < 6) picked = pickGreeting('night');
|
|
if (picked) text = picked;
|
|
}
|
|
|
|
var typeInterval;
|
|
var hideTimeout;
|
|
|
|
function runTypewriter(str, callback) {
|
|
if (typeInterval) clearInterval(typeInterval);
|
|
if (hideTimeout) clearTimeout(hideTimeout);
|
|
|
|
// Ensure visible
|
|
searchHintWrapper.classList.add('visible');
|
|
|
|
var i = 1;
|
|
// Pre-fill first char
|
|
if (str.length > 0) {
|
|
searchHintText.textContent = str.charAt(0);
|
|
}
|
|
|
|
typeInterval = setInterval(function() {
|
|
if (i >= str.length) {
|
|
clearInterval(typeInterval);
|
|
if (callback) callback();
|
|
return;
|
|
}
|
|
searchHintText.textContent = str.substring(0, i + 1);
|
|
i++;
|
|
}, 100);
|
|
}
|
|
|
|
// Check session storage for Jiaotailang tip
|
|
if (!sessionStorage.getItem('jiaotailang_shown')) {
|
|
// Initial Delay
|
|
setTimeout(function() {
|
|
// Step 1: Show Greeting
|
|
runTypewriter(text, function() {
|
|
// Animation Finished
|
|
|
|
// Step 2: Check Mobile Logic
|
|
if (window.isMobile && window.isMobile()) {
|
|
// Wait 2s then show secret hint
|
|
hideTimeout = setTimeout(function() {
|
|
var secretHint = "悄咪咪告诉你,点击随笔即可搜索";
|
|
runTypewriter(secretHint, function() {
|
|
// Finished secret hint
|
|
sessionStorage.setItem('jiaotailang_shown', 'true');
|
|
// Hide after 5s
|
|
hideTimeout = setTimeout(function() {
|
|
searchHintWrapper.classList.remove('visible');
|
|
}, 5000);
|
|
});
|
|
}, 2000);
|
|
} else {
|
|
// Desktop: Just hide after 8s
|
|
sessionStorage.setItem('jiaotailang_shown', 'true');
|
|
hideTimeout = setTimeout(function() {
|
|
searchHintWrapper.classList.remove('visible');
|
|
}, 8000);
|
|
}
|
|
});
|
|
}, 500);
|
|
} else {
|
|
// If already shown, set full text but don't auto-show
|
|
searchHintText.textContent = text;
|
|
}
|
|
|
|
// --- Hover Logic (Desktop) ---
|
|
if (!window.isMobile || !window.isMobile()) {
|
|
header.addEventListener('mouseenter', function() {
|
|
// Stop any running animations
|
|
if (typeInterval) clearInterval(typeInterval);
|
|
if (hideTimeout) clearTimeout(hideTimeout);
|
|
|
|
searchHintWrapper.classList.add('visible');
|
|
searchHintText.textContent = "点击随笔,开始搜索";
|
|
});
|
|
|
|
header.addEventListener('mouseleave', function() {
|
|
// Simply hide when leaving
|
|
searchHintWrapper.classList.remove('visible');
|
|
// Optional: Reset text to greeting in case it's shown again by some other logic?
|
|
// But mostly it will be triggered by hover again.
|
|
});
|
|
}
|
|
}
|
|
}
|