(function() { 'use strict'; // Live reload when studio saves settings const channel = new BroadcastChannel('writekit-studio'); channel.onmessage = function(event) { if (event.data.type === 'settings-changed') { location.reload(); } }; document.addEventListener('DOMContentLoaded', initSearch); function initSearch() { const trigger = document.getElementById('search-trigger'); const modal = document.getElementById('search-modal'); const backdrop = modal?.querySelector('.search-modal-backdrop'); const input = document.getElementById('search-input'); const results = document.getElementById('search-results'); if (!trigger || !modal || !input || !results) return; let debounceTimer; function open() { modal.classList.add('active'); document.body.style.overflow = 'hidden'; input.value = ''; results.innerHTML = ''; setTimeout(() => input.focus(), 10); } function close() { modal.classList.remove('active'); document.body.style.overflow = ''; } trigger.addEventListener('click', open); backdrop.addEventListener('click', close); document.addEventListener('keydown', function(e) { if (e.key === '/' && !modal.classList.contains('active') && !['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) { e.preventDefault(); open(); } if (e.key === 'Escape' && modal.classList.contains('active')) { close(); } }); input.addEventListener('input', function() { const query = this.value.trim(); clearTimeout(debounceTimer); if (query.length < 2) { results.innerHTML = ''; return; } debounceTimer = setTimeout(() => search(query), 150); }); input.addEventListener('keydown', function(e) { const items = results.querySelectorAll('.search-result'); const focused = results.querySelector('.search-result.focused'); if (e.key === 'ArrowDown') { e.preventDefault(); if (!focused && items.length) { items[0].classList.add('focused'); } else if (focused?.nextElementSibling) { focused.classList.remove('focused'); focused.nextElementSibling.classList.add('focused'); } } else if (e.key === 'ArrowUp') { e.preventDefault(); if (focused?.previousElementSibling) { focused.classList.remove('focused'); focused.previousElementSibling.classList.add('focused'); } } else if (e.key === 'Enter' && focused) { e.preventDefault(); const link = focused.querySelector('a'); if (link) window.location.href = link.href; } }); async function search(query) { try { const res = await fetch('/api/reader/search?q=' + encodeURIComponent(query)); const data = await res.json(); if (!data || data.length === 0) { results.innerHTML = '
No results found
'; return; } results.innerHTML = data.map(r => `
${highlight(r.title, query)}
${r.description ? `
${highlight(r.description, query)}
` : ''}
`).join(''); } catch (e) { results.innerHTML = '
Search failed
'; } } function highlight(text, query) { if (!text) return ''; const escaped = escapeHtml(text); const tokens = query.split(/\s+/).filter(t => t.length > 0); if (!tokens.length) return escaped; const pattern = tokens.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'); return escaped.replace(new RegExp(`(${pattern})`, 'gi'), '$1'); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } })();