(function () { 'use strict'; function showToast(message) { var container = document.getElementById('toast-container'); if (!container) return; var toast = document.createElement('div'); toast.className = 'toast'; toast.setAttribute('role', 'status'); toast.textContent = message; container.appendChild(toast); setTimeout(function () { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 2800); } window.PolarToast = showToast; function setTheme(theme, silent) { var allowed = ['light', 'dark', 'system']; if (allowed.indexOf(theme) === -1) theme = 'system'; document.documentElement.setAttribute('data-theme', theme); try { localStorage.setItem('polar_theme', theme); } catch (e) {} document.cookie = 'polar_theme=' + encodeURIComponent(theme) + '; path=/; max-age=31536000; SameSite=Lax'; document.querySelectorAll('[data-set-theme]').forEach(function (el) { var isCurrent = el.getAttribute('data-set-theme') === theme; el.classList.toggle('active', isCurrent); el.setAttribute('aria-checked', isCurrent ? 'true' : 'false'); }); } window.PolarSetTheme = setTheme; try { var currentAttr = document.documentElement.getAttribute('data-theme') || 'system'; document.querySelectorAll('[data-set-theme]').forEach(function (el) { el.classList.toggle('active', el.getAttribute('data-set-theme') === currentAttr); }); } catch (e) {} function toggleSidebar() { var html = document.documentElement; var isCollapsed = html.getAttribute('data-sidebar') === 'collapsed'; if (isCollapsed) { html.removeAttribute('data-sidebar'); try { localStorage.setItem('polar_sidebar', 'expanded'); } catch (e) {} } else { html.setAttribute('data-sidebar', 'collapsed'); try { localStorage.setItem('polar_sidebar', 'collapsed'); } catch (e) {} } } window.PolarToggleSidebar = toggleSidebar; function closeAllDropdowns() { document.querySelectorAll('[data-dropdown-menu].open').forEach(function (menu) { menu.classList.remove('open'); var parent = menu.closest('[data-dropdown]'); if (parent) { var btn = parent.querySelector('[data-dropdown-trigger]'); if (btn) btn.setAttribute('aria-expanded', 'false'); } }); } function openDialog(id) { var dlg = document.getElementById(id); if (!dlg) return; closeAllDropdowns(); dlg.classList.add('open'); dlg.setAttribute('aria-hidden', 'false'); var firstInput = dlg.querySelector('input:not([type="hidden"]), button[data-autofocus]'); if (firstInput) { setTimeout(function () { firstInput.focus(); }, 20); } } window.PolarOpenDialog = openDialog; function closeDialog(dlg) { if (!dlg) return; dlg.classList.remove('open'); dlg.setAttribute('aria-hidden', 'true'); } // Command Palette logic var paletteIndex = 0; function getVisiblePaletteItems() { var list = document.querySelectorAll('#command-palette [data-palette-item]'); var visible = []; list.forEach(function (item) { if (item.style.display !== 'none') visible.push(item); }); return visible; } function updatePaletteSelection() { var items = getVisiblePaletteItems(); if (paletteIndex >= items.length) paletteIndex = 0; if (paletteIndex < 0) paletteIndex = Math.max(0, items.length - 1); items.forEach(function (el, idx) { el.classList.toggle('selected', idx === paletteIndex); if (idx === paletteIndex) { el.scrollIntoView({ block: 'nearest' }); } }); } function filterPalette(query) { var q = (query || '').trim().toLowerCase(); var allItems = document.querySelectorAll('#command-palette [data-palette-item]'); allItems.forEach(function (item) { var text = (item.getAttribute('data-palette-keywords') || item.textContent || '').toLowerCase(); var show = q === '' || text.indexOf(q) !== -1; item.style.display = show ? '' : 'none'; }); document.querySelectorAll('#command-palette [data-palette-group]').forEach(function (group) { var hasVisible = false; group.querySelectorAll('[data-palette-item]').forEach(function (it) { if (it.style.display !== 'none') hasVisible = true; }); group.style.display = hasVisible ? '' : 'none'; }); paletteIndex = 0; updatePaletteSelection(); } function openPalette(initialQuery) { var palette = document.getElementById('command-palette'); if (!palette) return; closeAllDropdowns(); palette.classList.add('open'); palette.setAttribute('aria-hidden', 'false'); var input = document.getElementById('command-palette-input'); if (input) { input.value = initialQuery || ''; filterPalette(input.value); setTimeout(function () { input.focus(); if (initialQuery) input.select(); }, 20); } } window.PolarOpenPalette = openPalette; function activatePaletteItem(item) { if (!item) return; var palette = document.getElementById('command-palette'); var href = item.getAttribute('data-palette-href'); var theme = item.getAttribute('data-palette-theme'); var action = item.getAttribute('data-palette-action'); if (palette) closeDialog(palette); if (theme) { setTheme(theme, false); } else if (action === 'shortcuts') { openDialog('shortcuts-dialog'); } else if (action === 'toggle-sidebar') { toggleSidebar(); } else if (href) { window.location.href = href; } } document.addEventListener('click', function (e) { // Toggle sidebar var sbBtn = e.target.closest('[data-toggle-sidebar]'); if (sbBtn) { e.preventDefault(); toggleSidebar(); return; } // Dropdown trigger var trigger = e.target.closest('[data-dropdown-trigger]'); if (trigger) { e.stopPropagation(); var dropdown = trigger.closest('[data-dropdown]'); var menu = dropdown ? dropdown.querySelector('[data-dropdown-menu]') : null; var wasOpen = menu && menu.classList.contains('open'); closeAllDropdowns(); if (menu && !wasOpen) { menu.classList.add('open'); trigger.setAttribute('aria-expanded', 'true'); } return; } // Theme button var themeBtn = e.target.closest('[data-set-theme]'); if (themeBtn) { e.preventDefault(); setTheme(themeBtn.getAttribute('data-set-theme'), false); closeAllDropdowns(); return; } // Open Command Palette var paletteTrigger = e.target.closest('[data-open-palette]'); if (paletteTrigger) { e.preventDefault(); openPalette(paletteTrigger.getAttribute('data-open-palette') || ''); return; } // Palette item click var paletteItem = e.target.closest('[data-palette-item]'); if (paletteItem) { e.preventDefault(); activatePaletteItem(paletteItem); return; } // Dialog open var dlgOpen = e.target.closest('[data-dialog-open]'); if (dlgOpen) { e.preventDefault(); openDialog(dlgOpen.getAttribute('data-dialog-open')); return; } // Dialog close var dlgClose = e.target.closest('[data-dialog-close]'); if (dlgClose) { e.preventDefault(); closeDialog(dlgClose.closest('.backdrop')); return; } // Backdrop click if (e.target.classList.contains('backdrop')) { closeDialog(e.target); return; } // Mobile drawer var drawerOpen = e.target.closest('[data-drawer-open]'); if (drawerOpen) { var drw = document.getElementById('mobile-drawer'); if (drw) drw.classList.add('open'); return; } var drawerClose = e.target.closest('[data-drawer-close]'); if (drawerClose || e.target.id === 'mobile-drawer') { var drw2 = document.getElementById('mobile-drawer'); if (drw2) drw2.classList.remove('open'); return; } // Copy to clipboard var copyBtn = e.target.closest('[data-copy]'); if (copyBtn) { e.preventDefault(); var text = copyBtn.getAttribute('data-copy'); var copiedMsg = document.documentElement.getAttribute('data-copied-text') || 'Copied'; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(function () { showToast(copiedMsg); }); } else { var ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); showToast(copiedMsg); } return; } if (!e.target.closest('[data-dropdown-menu]')) { closeAllDropdowns(); } }); // Input handlers (palette filter + delete repo confirmation match) document.addEventListener('input', function (e) { if (e.target.id === 'command-palette-input') { filterPalette(e.target.value); return; } var matchExpected = e.target.getAttribute('data-confirm-input'); if (matchExpected !== null) { var btnId = e.target.getAttribute('data-confirm-button'); var btn = btnId ? document.getElementById(btnId) : null; if (btn) { btn.disabled = e.target.value.trim() !== matchExpected; } } if (e.target.hasAttribute('data-file-filter')) { var q = e.target.value.trim().toLowerCase(); document.querySelectorAll('[data-tree-row]').forEach(function (row) { var name = (row.getAttribute('data-tree-name') || '').toLowerCase(); row.style.display = !q || name.indexOf(q) !== -1 ? '' : 'none'; }); } }); // Keyboard navigation & chords var lastChordKey = ''; var lastChordTime = 0; document.addEventListener('keydown', function (e) { var palette = document.getElementById('command-palette'); var isPaletteOpen = palette && palette.classList.contains('open'); if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); if (isPaletteOpen) { closeDialog(palette); } else { openPalette(''); } return; } if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'b') { e.preventDefault(); toggleSidebar(); return; } if (e.key === 'Escape') { closeAllDropdowns(); document.querySelectorAll('.backdrop.open').forEach(function (b) { closeDialog(b); }); var drw = document.getElementById('mobile-drawer'); if (drw) drw.classList.remove('open'); return; } if (isPaletteOpen) { var items = getVisiblePaletteItems(); if (e.key === 'ArrowDown') { e.preventDefault(); paletteIndex = (paletteIndex + 1) % Math.max(1, items.length); updatePaletteSelection(); } else if (e.key === 'ArrowUp') { e.preventDefault(); paletteIndex = (paletteIndex - 1 + Math.max(1, items.length)) % Math.max(1, items.length); updatePaletteSelection(); } else if (e.key === 'Enter') { e.preventDefault(); if (items[paletteIndex]) activatePaletteItem(items[paletteIndex]); } return; } var tag = e.target && e.target.tagName ? e.target.tagName.toUpperCase() : ''; if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || (e.target && e.target.isContentEditable)) { return; } if (e.ctrlKey || e.metaKey || e.altKey) return; var now = Date.now(); if (lastChordKey === 'g' && now - lastChordTime < 900) { lastChordKey = ''; if (e.key.toLowerCase() === 'h') { e.preventDefault(); window.location.href = '/'; return; } if (e.key.toLowerCase() === 'r') { e.preventDefault(); window.location.href = '/explore'; return; } } if (e.key.toLowerCase() === 'g') { lastChordKey = 'g'; lastChordTime = now; return; } if (e.key === '/') { e.preventDefault(); var searchInput = document.querySelector('[data-search-input]'); if (searchInput) { searchInput.focus(); } else { openPalette(''); } return; } if (e.key === '?') { e.preventDefault(); openDialog('shortcuts-dialog'); return; } if (e.key.toLowerCase() === 't') { e.preventDefault(); var fileFilter = document.querySelector('[data-file-filter]'); if (fileFilter) { fileFilter.focus(); } else { openPalette('file:'); } } }); // Avatar file picker preview & client-side 256x256 square crop/resize document.addEventListener('change', function (e) { if (e.target && e.target.id === 'avatar-file-input') { var file = e.target.files && e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function (ev) { var img = new Image(); img.onload = function () { var size = 256; var canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; var ctx = canvas.getContext('2d'); var minSide = Math.min(img.width, img.height); var sx = (img.width - minSide) / 2; var sy = (img.height - minSide) / 2; ctx.drawImage(img, sx, sy, minSide, minSide, 0, 0, size, size); var dataUrl = canvas.toDataURL('image/webp', 0.88); if (!dataUrl || dataUrl.indexOf('data:image/webp') !== 0) { dataUrl = canvas.toDataURL('image/jpeg', 0.88); } var hidden = document.getElementById('avatar-data-input'); var rmHidden = document.getElementById('remove-avatar-input'); if (hidden) hidden.value = dataUrl; if (rmHidden) rmHidden.value = 'false'; var previewWrap = document.getElementById('avatar-preview-wrap'); if (previewWrap) { previewWrap.innerHTML = 'Avatar'; } }; img.src = ev.target.result; }; reader.readAsDataURL(file); } }); document.addEventListener('click', function (e) { var rmBtn = e.target.closest('#remove-avatar-btn'); if (rmBtn) { e.preventDefault(); var rmHidden = document.getElementById('remove-avatar-input'); var hidden = document.getElementById('avatar-data-input'); var fileInput = document.getElementById('avatar-file-input'); if (rmHidden) rmHidden.value = 'true'; if (hidden) hidden.value = ''; if (fileInput) fileInput.value = ''; var previewWrap = document.getElementById('avatar-preview-wrap'); if (previewWrap) { previewWrap.innerHTML = '?'; } rmBtn.style.display = 'none'; } }); // Repo header: the description stays on the title line and expands onto its // own line when clicked. The avatar is sized to the title line so it always // sits on the same row as the repository name, at a modest size. var repoAvatarTimer = null; function sizeRepoAvatars() { var changed = false; document.querySelectorAll('.repo-banner').forEach(function (banner) { var avatar = banner.querySelector('.repo-avatar'); var title = banner.querySelector('.repo-head-row nav'); if (!avatar || !title) return; var h = Math.round(title.getBoundingClientRect().height); if (h <= 0) return; var size = Math.min(30, Math.max(18, h)); var sizePx = size + 'px'; if (avatar.style.width === sizePx) return; avatar.style.width = sizePx; avatar.style.height = sizePx; avatar.style.fontSize = Math.min(20, Math.max(9, Math.round(size * 0.42))) + 'px'; changed = true; }); return changed; } function updateRepoDescs() { document.querySelectorAll('[data-repo-desc]').forEach(function (el) { var banner = el.closest('.repo-banner'); if (banner && banner.classList.contains('desc-open')) { el.classList.add('truncated'); return; } el.classList.toggle('truncated', el.scrollWidth > el.clientWidth + 1); }); if (sizeRepoAvatars()) { // Re-measure once the width transition settles: the final avatar width // changes the room left for the description. window.clearTimeout(repoAvatarTimer); repoAvatarTimer = window.setTimeout(sizeRepoAvatars, 300); } } document.addEventListener('click', function (e) { var desc = e.target.closest('[data-repo-desc]'); if (!desc || !desc.classList.contains('truncated')) return; var banner = desc.closest('.repo-banner'); if (!banner) return; banner.classList.toggle('desc-open'); updateRepoDescs(); }); window.addEventListener('resize', updateRepoDescs); document.addEventListener('htmx:afterSwap', updateRepoDescs); updateRepoDescs(); })();