(function() { const logoutButton = document.querySelector('.site-nav button[onclick="logout()"]'); const authStatusEl = document.querySelector('[data-auth-status]'); window.auth = async function() { const password = window.prompt('Enter password'); if (!password) return; const resp = await fetch('/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }) }); if (resp.ok) { await refreshAuth(); } else { alert('Auth failed'); } }; window.logout = async function() { await fetch('/auth/logout', { method: 'POST' }); await refreshAuth(); }; async function refreshAuth() { const resp = await fetch('/auth/status'); if (!resp.ok) return; const data = await resp.json(); window.LOOKBOOK_AUTH = data.authenticated; if (logoutButton) { logoutButton.hidden = !data.authenticated; } if (authStatusEl) { authStatusEl.textContent = data.authenticated ? 'Authed' : 'Read only'; } const gated = document.querySelectorAll('[data-auth-required]'); gated.forEach((el) => { el.toggleAttribute('hidden', !data.authenticated); }); } const tagInput = document.querySelector('[data-tag-input]'); const tagSuggestions = document.querySelector('[data-tag-suggestions]'); const tagForm = document.querySelector('[data-tag-form]'); const tagEditor = document.querySelector('[data-tag-editor]'); const tagToggle = document.querySelector('[data-tag-toggle]'); const tagList = document.querySelector('[data-tag-list]'); const tagData = tagSuggestions ? JSON.parse(tagSuggestions.getAttribute('data-tags') || '[]') : []; if (tagToggle && tagEditor) { tagToggle.addEventListener('click', () => { const isHidden = tagEditor.hasAttribute('hidden'); tagEditor.toggleAttribute('hidden', !isHidden); }); } if (tagList && tagEditor) { tagList.addEventListener('click', () => { if (!window.LOOKBOOK_AUTH) return; const isHidden = tagEditor.hasAttribute('hidden'); tagEditor.toggleAttribute('hidden', !isHidden); }); } if (tagSuggestions) { if (tagInput) { tagInput.addEventListener('input', () => renderSuggestions(tagInput.value)); renderSuggestions(tagInput.value); } else { renderSuggestions(''); } } function renderSuggestions(value) { if (!tagSuggestions) return; const query = (value || '').trim().toLowerCase(); tagSuggestions.innerHTML = ''; const matches = fuzzyMatchTags(query, tagData).slice(0, 10); matches.forEach((tag) => { const button = document.createElement('button'); button.type = 'button'; button.textContent = tag; button.addEventListener('click', () => { if (tagInput) { addTag(tag); return; } const target = Array.from(filterButtons).find((btn) => btn.dataset.tagFilter === tag); if (target) target.click(); }); tagSuggestions.appendChild(button); }); } function addTag(tag) { if (!tagInput) return; const tags = parseTags(tagInput.value); if (!tags.includes(tag)) tags.push(tag); tagInput.value = tags.join(', '); renderSuggestions(tagInput.value); } function parseTags(value) { return value .split(',') .map((tag) => tag.trim()) .filter((tag) => tag.length > 0); } function fuzzyMatchTags(query, tags) { if (!query) return tags; const scores = tags.map((tag) => ({ tag, score: fuzzyScore(tag, query) })) .filter((entry) => entry.score > 0) .sort((a, b) => b.score - a.score || a.tag.localeCompare(b.tag)); return scores.map((entry) => entry.tag); } function fuzzyScore(tag, query) { let score = 0; let ti = 0; for (const qc of query) { const idx = tag.indexOf(qc, ti); if (idx === -1) return 0; score += idx === ti ? 3 : 1; ti = idx + 1; } return score; } const filterButtons = document.querySelectorAll('[data-tag-filter]'); const gridItems = document.querySelectorAll('[data-item-tags]'); const selectedTags = new Set(); filterButtons.forEach((button) => { button.addEventListener('click', (event) => { const tag = button.dataset.tagFilter; const multi = event.ctrlKey || event.metaKey; if (!multi) { selectedTags.clear(); filterButtons.forEach((b) => b.classList.remove('active')); } if (selectedTags.has(tag)) { selectedTags.delete(tag); button.classList.remove('active'); } else { selectedTags.add(tag); button.classList.add('active'); } applyFilters(); }); }); function applyFilters() { const selected = Array.from(selectedTags); gridItems.forEach((item) => { const tags = (item.getAttribute('data-item-tags') || '').split(',').map((t) => t.trim()).filter(Boolean); if (selected.length === 0) { item.removeAttribute('hidden'); return; } const matches = selected.some((tag) => tags.includes(tag)); item.toggleAttribute('hidden', !matches); }); } if (tagForm) { tagForm.addEventListener('submit', async (event) => { event.preventDefault(); const formData = new FormData(tagForm); const resp = await fetch(tagForm.action, { method: 'POST', body: formData }); if (resp.ok) { window.location.reload(); } }); } refreshAuth(); })();