Files
blog/themes/Ying/assets/js/modules/search.js
T

433 lines
17 KiB
JavaScript
Raw Normal View History

2026-01-31 19:24:22 +08:00
window.initSearch = function() {
initSearchModal();
initInlineSearch();
};
2026-02-01 17:00:25 +08:00
var searchLoadingPromise = null;
2026-01-31 19:24:22 +08:00
function loadSearchIndex() {
2026-02-01 17:00:25 +08:00
if (window.searchIndex) {
return Promise.resolve(window.searchIndex);
2026-01-31 19:24:22 +08:00
}
2026-02-01 17:00:25 +08:00
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;
2026-01-31 19:24:22 +08:00
}
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;
// Close modal when a result is clicked
searchResults.addEventListener('click', function(e) {
if (e.target.closest('a')) {
searchModal.classList.remove('show');
}
});
searchInput.addEventListener('input', function() {
var query = this.value.trim().toLowerCase();
2026-02-01 17:00:25 +08:00
if (query.length === 0) {
2026-01-31 19:24:22 +08:00
searchResults.innerHTML = '<p class="search-tip">输入关键词并回车进行搜索</p>';
return;
}
2026-02-01 17:00:25 +08:00
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;
}
2026-01-31 19:24:22 +08:00
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) {
return `
<div class="search-result-item">
<a href="${item.permalink}" class="search-result-title">${item.title}</a>
<span class="search-result-date">${item.date}</span>
<div class="search-result-summary">${item.summary || ''}</div>
</div>
`;
}).join('');
searchResults.innerHTML = html;
// Refresh PJAX to recognize new links
if (window.pjax) {
window.pjax.refresh(searchResults);
}
} else {
searchResults.innerHTML = '<p class="search-tip">未找到相关文章</p>';
}
});
}
}
}
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) {
2026-02-01 17:00:25 +08:00
if (!header || !searchContainer || !input || !resultsContainer) {
console.warn('Search elements missing, cannot toggle mode.');
return;
}
2026-01-31 19:24:22 +08:00
2026-02-01 17:00:25 +08:00
var searchHint = document.querySelector('.search-hint-wrapper');
2026-01-31 19:24:22 +08:00
if (show) {
2026-02-01 17:00:25 +08:00
if (searchHint) searchHint.style.display = 'none'; // Hide hint completely to avoid layout shifts
2026-01-31 19:24:22 +08:00
header.style.display = 'none';
searchContainer.style.display = 'block';
input.focus();
2026-02-01 17:00:25 +08:00
loadSearchIndex().then(() => {
console.log('Search index loaded via toggle');
}).catch(e => console.error('Search index load failed', e));
2026-01-31 21:27:06 +08:00
// Add Esc key listener
document.addEventListener('keydown', escListener);
2026-01-31 19:24:22 +08:00
} else {
2026-02-01 17:00:25 +08:00
if (searchHint) searchHint.style.display = ''; // Restore hint
2026-01-31 19:24:22 +08:00
header.style.display = 'inline-flex';
searchContainer.style.display = 'none';
input.value = '';
2026-01-31 21:27:06 +08:00
// Remove .has-value class
searchContainer.querySelector('.search-input-group').classList.remove('has-value');
2026-01-31 19:24:22 +08:00
resultsContainer.classList.remove('show');
resultsContainer.innerHTML = '';
2026-01-31 21:27:06 +08:00
// Remove Esc key listener
document.removeEventListener('keydown', escListener);
}
};
var escListener = function(e) {
if (e.key === 'Escape') {
window.toggleSearchMode(false);
2026-01-31 19:24:22 +08:00
}
};
if (!header || !searchContainer || !input || !resultsContainer) return;
// Input Event
var newInput = input.cloneNode(true);
input.parentNode.replaceChild(newInput, input);
input = newInput;
2026-01-31 22:52:19 +08:00
// Handle Search Icon Click (Focus input)
var searchIcon = searchContainer.querySelector('.search-icon');
if (searchIcon) {
searchIcon.addEventListener('click', function() {
input.focus();
});
}
2026-01-31 21:27:06 +08:00
// 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);
}
});
}
2026-01-31 19:24:22 +08:00
input.addEventListener('input', function() {
var query = this.value.trim().toLowerCase();
2026-01-31 21:27:06 +08:00
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');
}
2026-01-31 19:24:22 +08:00
if (query.length === 0) {
resultsContainer.classList.remove('show');
resultsContainer.innerHTML = '';
return;
}
2026-02-01 17:00:25 +08:00
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;
}
2026-01-31 19:24:22 +08:00
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) {
2026-02-01 17:00:25 +08:00
var html = results.slice(0, 5).map(function(item) {
return `
<a href="${item.permalink}" class="search-result-item">
<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>
</a>
`;
}).join('');
resultsContainer.innerHTML = html;
resultsContainer.classList.add('show');
// PJAX Refresh
if (window.pjax) {
window.pjax.refresh(resultsContainer);
}
} else {
2026-01-31 21:27:06 +08:00
resultsContainer.innerHTML = '<div class="search-result-item no-result">暂无相关内容</div>';
2026-01-31 19:24:22 +08:00
resultsContainer.classList.add('show');
}
});
// Close on click outside
if (!window.inlineSearchClickOutside) {
window.inlineSearchClickOutside = function(e) {
var header = document.getElementById('essay-header');
var searchContainer = document.getElementById('search-container');
var resultsContainer = document.getElementById('inline-search-results');
if (header && searchContainer && resultsContainer && searchContainer.style.display === 'block') {
if (!searchContainer.contains(e.target) && !header.contains(e.target) && !resultsContainer.contains(e.target)) {
window.toggleSearchMode(false);
}
}
};
document.addEventListener('click', window.inlineSearchClickOutside);
}
2026-02-01 17:00:25 +08:00
// Manual PJAX delegation for results
resultsContainer.addEventListener('click', function(e) {
var link = e.target.closest('a');
if (link && window.pjax) {
e.preventDefault();
// Close search first
window.toggleSearchMode(false);
// Then navigate
window.pjax.loadUrl(link.href, { triggerElement: link });
}
});
// 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) {
if (hour >= 6 && hour < 11 && window.searchGreetings.morning) {
text = window.searchGreetings.morning;
} else if (hour >= 11 && hour < 14 && window.searchGreetings.noon) {
text = window.searchGreetings.noon;
} else if (hour >= 14 && hour < 18 && window.searchGreetings.afternoon) {
text = window.searchGreetings.afternoon;
} else if (hour >= 18 && hour < 22 && window.searchGreetings.evening) {
text = window.searchGreetings.evening;
} else if ((hour >= 22 || hour < 6) && window.searchGreetings.night) {
text = window.searchGreetings.night;
}
}
2026-02-02 20:38:58 +08:00
var typeInterval;
var hideTimeout;
function runTypewriter(str, callback) {
if (typeInterval) clearInterval(typeInterval);
if (hideTimeout) clearTimeout(hideTimeout);
// Ensure visible
2026-02-01 17:00:25 +08:00
searchHintWrapper.classList.add('visible');
var i = 1;
2026-02-02 20:38:58 +08:00
// Pre-fill first char
if (str.length > 0) {
searchHintText.textContent = str.charAt(0);
}
typeInterval = setInterval(function() {
if (i >= str.length) {
2026-02-01 17:00:25 +08:00
clearInterval(typeInterval);
2026-02-02 20:38:58 +08:00
if (callback) callback();
2026-02-01 17:00:25 +08:00
return;
}
2026-02-02 20:38:58 +08:00
searchHintText.textContent = str.substring(0, i + 1);
2026-02-01 17:00:25 +08:00
i++;
}, 100);
2026-02-02 20:38:58 +08:00
}
// 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() {
2026-02-02 20:53:04 +08:00
var secretHint = "悄咪咪告诉你,点击随笔即可搜索";
2026-02-02 20:38:58 +08:00
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.
});
}
2026-02-01 17:00:25 +08:00
}
2026-01-31 19:24:22 +08:00
}