(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 copyText(text, successMessage) { var value = text == null ? '' : String(text); if (!value) return; var copiedMsg = successMessage || document.documentElement.getAttribute('data-copied-text') || 'Copied'; var failedMsg = document.documentElement.getAttribute('data-copy-failed-text') || 'Could not copy'; function fallbackCopy() { var previousFocus = document.activeElement; var ta = document.createElement('textarea'); ta.value = value; ta.setAttribute('readonly', ''); ta.style.position = 'fixed'; ta.style.left = '-9999px'; ta.style.top = '0'; document.body.appendChild(ta); ta.focus(); ta.select(); ta.setSelectionRange(0, value.length); var ok = false; try { ok = document.execCommand('copy'); } catch (err) { ok = false; } if (ta.parentNode) ta.parentNode.removeChild(ta); if (previousFocus && previousFocus.focus) { try { previousFocus.focus({ preventScroll: true }); } catch (err2) {} } showToast(ok ? copiedMsg : failedMsg); } if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(value).then(function () { showToast(copiedMsg); }, fallbackCopy); } else { fallbackCopy(); } } 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'); } }); } var contextMenuIndex = -1; var contextMenuState = null; function contextElement(target) { if (!target) return null; if (target.nodeType === 1) return target; return target.parentElement || null; } function isEditableTarget(target) { if (!target || !target.tagName) return false; var tag = target.tagName.toUpperCase(); return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable; } function getContextMenuItems() { var menu = document.getElementById('context-menu'); if (!menu) return []; return Array.prototype.filter.call(menu.querySelectorAll('.context-menu-item'), function (item) { var section = item.closest('[data-context-section]'); return !section || !section.hidden; }); } function focusContextMenuItem(index) { var items = getContextMenuItems(); if (!items.length) return; contextMenuIndex = (index + items.length) % items.length; items.forEach(function (item, idx) { item.classList.toggle('is-active', idx === contextMenuIndex); }); try { items[contextMenuIndex].focus({ preventScroll: true }); } catch (err) { items[contextMenuIndex].focus(); } } function closeContextMenu() { var menu = document.getElementById('context-menu'); if (!menu || !menu.classList.contains('open')) return; var restoreFocus = contextMenuState && contextMenuState.restoreFocus; menu.classList.remove('open'); menu.setAttribute('aria-hidden', 'true'); menu.style.left = ''; menu.style.top = ''; contextMenuState = null; contextMenuIndex = -1; menu.querySelectorAll('.context-menu-item.is-active').forEach(function (item) { item.classList.remove('is-active'); }); if (restoreFocus && restoreFocus !== document.body && restoreFocus.isConnected) { try { restoreFocus.focus({ preventScroll: true }); } catch (err) {} } } function openContextMenuAt(x, y, target, restoreFocus) { var menu = document.getElementById('context-menu'); var el = contextElement(target); if (!menu || !el) return; closeAllDropdowns(); var link = el.closest ? el.closest('a[href]') : null; if (link && (!link.getAttribute('href') || /^javascript:/i.test(link.getAttribute('href')))) { link = null; } var image = el.closest ? el.closest('img') : null; var imageURL = image ? (image.currentSrc || image.src || '') : ''; var imageIsData = /^data:/i.test(imageURL); var imageIsTemporary = /^(data|blob):/i.test(imageURL); var selectedText = ''; try { var selection = window.getSelection(); if (selection && !selection.isCollapsed) selectedText = selection.toString().trim(); } catch (err) {} var searchText = selectedText.replace(/\s+/g, ' ').trim().slice(0, 160); var linkText = ''; if (link) { linkText = (link.innerText || link.textContent || link.getAttribute('aria-label') || link.getAttribute('title') || '') .replace(/\s+/g, ' ').trim(); } contextMenuState = { linkHref: link ? link.href : '', linkText: linkText, imageURL: imageURL, imageIsData: imageIsData, selectedText: selectedText, searchText: searchText, restoreFocus: restoreFocus || document.activeElement }; ['selection', 'link', 'image'].forEach(function (name) { var section = menu.querySelector('[data-context-section="' + name + '"]'); if (section) { section.hidden = name === 'selection' ? !selectedText : name === 'link' ? !link : !imageURL; } }); var linkTextItem = menu.querySelector('[data-context-action="link-text"]'); if (linkTextItem) linkTextItem.hidden = !linkText; var imageOpenItem = menu.querySelector('[data-context-action="image-open"]'); if (imageOpenItem) imageOpenItem.hidden = !imageURL || imageIsData; var imageCopyItem = menu.querySelector('[data-context-action="image-copy"]'); if (imageCopyItem) imageCopyItem.hidden = !imageURL || imageIsTemporary; menu.classList.add('open'); menu.setAttribute('aria-hidden', 'false'); menu.scrollTop = 0; menu.style.left = '0px'; menu.style.top = '0px'; var menuWidth = menu.offsetWidth; var menuHeight = menu.offsetHeight; var gap = 8; var vw = window.innerWidth || document.documentElement.clientWidth; var vh = window.innerHeight || document.documentElement.clientHeight; var left = x; var top = y; if (left + menuWidth > vw - gap) left = x - menuWidth; if (left < gap) left = gap; if (left + menuWidth > vw - gap) left = Math.max(gap, vw - menuWidth - gap); if (top + menuHeight > vh - gap) top = y - menuHeight; if (top < gap) top = gap; if (top + menuHeight > vh - gap) top = Math.max(gap, vh - menuHeight - gap); menu.style.left = Math.round(left) + 'px'; menu.style.top = Math.round(top) + 'px'; contextMenuIndex = -1; setTimeout(function () { if (menu.classList.contains('open')) focusContextMenuItem(0); }, 20); } function openContextMenuForKeyboard() { var target = document.activeElement; if (!target || target === document.body) target = document.body; var rect = target.getBoundingClientRect ? target.getBoundingClientRect() : null; var x = rect ? rect.left + Math.min(rect.width, 20) : 16; var y = rect ? rect.bottom + 4 : 16; openContextMenuAt(x, y, target, target); } function runContextMenuAction(item) { var action = item.getAttribute('data-context-action'); var state = contextMenuState || {}; closeContextMenu(); if (action === 'copy-selection') { copyText(state.selectedText); } else if (action === 'search-selection') { openPalette(state.searchText || state.selectedText || ''); } else if (action === 'link-open') { if (state.linkHref) window.open(state.linkHref, '_blank', 'noopener,noreferrer'); } else if (action === 'link-copy') { copyText(state.linkHref); } else if (action === 'link-text') { copyText(state.linkText); } else if (action === 'image-open') { if (state.imageURL) window.open(state.imageURL, '_blank', 'noopener,noreferrer'); } else if (action === 'image-copy') { copyText(state.imageURL); } else if (action === 'image-save') { if (state.imageURL) { var extension = 'img'; var dataMatch = state.imageURL.match(/^data:image\/([a-z0-9.+-]+)/i); if (dataMatch) { extension = dataMatch[1].toLowerCase().replace('jpeg', 'jpg').replace('svg+xml', 'svg'); } else { try { var pathMatch = new URL(state.imageURL).pathname.match(/\.([a-z0-9]{2,5})$/i); if (pathMatch) extension = pathMatch[1].toLowerCase(); } catch (err) {} } var download = document.createElement('a'); download.href = state.imageURL; download.download = 'polar-image.' + extension; document.body.appendChild(download); download.click(); document.body.removeChild(download); } } else if (action === 'page-copy') { copyText(window.location.href); } else if (action === 'page-reload') { window.location.reload(); } else if (action === 'page-back') { window.history.back(); } else if (action === 'page-forward') { window.history.forward(); } } function cycleTheme() { var current = document.documentElement.getAttribute('data-theme') || 'system'; var order = ['light', 'dark', 'system']; var next = order[(order.indexOf(current) + 1) % order.length]; setTheme(next, false); var item = document.querySelector('#command-palette [data-palette-theme="' + next + '"]'); showToast(item ? item.textContent.trim() : next); } function shortcutUnavailable() { showToast(document.documentElement.getAttribute('data-shortcut-unavailable') || 'Shortcut unavailable here'); } function handleNavigationChord(key) { var body = document.body; var repoPath = body ? body.getAttribute('data-repo-path') : ''; var currentUser = body ? body.getAttribute('data-current-user') : ''; var repoFlags = { issues: body && body.getAttribute('data-repo-issues') === 'true', pulls: body && body.getAttribute('data-repo-pulls') === 'true', releases: body && body.getAttribute('data-repo-releases') === 'true', activity: body && body.getAttribute('data-repo-activity') === 'true', canWrite: body && body.getAttribute('data-repo-can-write') === 'true' }; if (key === 'h') { window.location.href = '/'; return true; } if (key === 'r') { window.location.href = '/explore'; return true; } if (key === 'o') { if (!currentUser) { shortcutUnavailable(); return true; } window.location.href = '/' + encodeURIComponent(currentUser); return true; } if (key === 'n') { if (!currentUser) { shortcutUnavailable(); return true; } window.location.href = '/new'; return true; } if (key === 's' && !repoPath) { if (currentUser) { window.location.href = '/settings'; } else { shortcutUnavailable(); } return true; } if (!repoPath) { var isRepoChord = 'ciplab'.indexOf(key) !== -1; if (isRepoChord) shortcutUnavailable(); return isRepoChord; } if (key === 'c') { window.location.href = repoPath; return true; } if (key === 'i') { if (!repoFlags.issues) shortcutUnavailable(); else window.location.href = repoPath + '/issues'; return true; } if (key === 'p') { if (!repoFlags.pulls) shortcutUnavailable(); else window.location.href = repoPath + '/pulls'; return true; } if (key === 'l') { if (!repoFlags.releases) shortcutUnavailable(); else window.location.href = repoPath + '/releases'; return true; } if (key === 'a') { if (!repoFlags.activity) shortcutUnavailable(); else window.location.href = repoPath + '/activity'; return true; } if (key === 'b') { window.location.href = repoPath + '/branches'; return true; } if (key === 's') { if (!repoFlags.canWrite) shortcutUnavailable(); else window.location.href = repoPath + '/settings'; return true; } return false; } document.addEventListener('contextmenu', function (e) { var el = contextElement(e.target); if (!el || el.closest('#context-menu') || el.isContentEditable || el.closest('input, textarea, select, [data-native-context]')) { return; } e.preventDefault(); openContextMenuAt(e.clientX, e.clientY, el, document.activeElement); }); document.addEventListener('pointerdown', function (e) { var menu = document.getElementById('context-menu'); if (!menu || !menu.classList.contains('open')) return; if (!e.target.closest || !e.target.closest('#context-menu')) closeContextMenu(); }, true); document.addEventListener('scroll', function (e) { var target = contextElement(e.target); if (target && target.closest && target.closest('#context-menu')) return; closeContextMenu(); }, true); window.addEventListener('resize', closeContextMenu); window.addEventListener('popstate', closeContextMenu); window.addEventListener('hashchange', closeContextMenu); document.addEventListener('htmx:beforeSwap', closeContextMenu); function openDialog(id) { var dlg = document.getElementById(id); if (!dlg) return; closeAllDropdowns(); closeContextMenu(); 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(); closeContextMenu(); 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) { // Context menu action var contextAction = e.target.closest('[data-context-action]'); if (contextAction) { e.preventDefault(); runContextMenuAction(contextAction); return; } // Context menu navigation links if (e.target.closest('#context-menu a[href]')) { closeContextMenu(); return; } if (e.target.closest('#context-menu')) return; // 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(); copyText(copyBtn.getAttribute('data-copy')); 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'); var contextMenu = document.getElementById('context-menu'); var isContextMenuOpen = contextMenu && contextMenu.classList.contains('open'); if (isContextMenuOpen) { if (e.key === 'Escape') { e.preventDefault(); closeContextMenu(); return; } if (e.key === 'ArrowDown') { e.preventDefault(); focusContextMenuItem(contextMenuIndex + 1); return; } if (e.key === 'ArrowUp') { e.preventDefault(); focusContextMenuItem(contextMenuIndex - 1); return; } if (e.key === 'Home') { e.preventDefault(); focusContextMenuItem(0); return; } if (e.key === 'End') { e.preventDefault(); focusContextMenuItem(getContextMenuItems().length - 1); return; } if (e.key === 'Tab') { closeContextMenu(); return; } if (!e.ctrlKey && !e.metaKey && !e.altKey) return; } if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 'l') { e.preventDefault(); cycleTheme(); return; } 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') { closeContextMenu(); 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; } if ((e.key === 'F10' && e.shiftKey) || e.key === 'ContextMenu') { if (isEditableTarget(e.target)) return; e.preventDefault(); openContextMenuForKeyboard(); return; } if (isEditableTarget(e.target)) return; if (e.ctrlKey || e.metaKey || e.altKey) return; var key = e.key.toLowerCase(); var now = Date.now(); if (lastChordKey === 'g' && now - lastChordTime < 900) { lastChordKey = ''; lastChordTime = 0; if (handleNavigationChord(key)) { e.preventDefault(); return; } } else if (key !== 'g') { lastChordKey = ''; } if (key === 'g') { lastChordKey = 'g'; lastChordTime = now; return; } if (key === 'y') { e.preventDefault(); copyText(window.location.href); 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 (key === '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'; } }); function resizeCoverImage(file, width, height, callback) { var reader = new FileReader(); reader.onload = function (ev) { var img = new Image(); img.onload = function () { var canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; var ctx = canvas.getContext('2d'); var scale = Math.max(width / img.width, height / img.height); var sw = width / scale; var sh = height / scale; var sx = (img.width - sw) / 2; var sy = (img.height - sh) / 2; ctx.drawImage(img, sx, sy, sw, sh, 0, 0, width, height); var dataUrl = canvas.toDataURL('image/webp', 0.84); if (!dataUrl || dataUrl.indexOf('data:image/webp') !== 0) { dataUrl = canvas.toDataURL('image/jpeg', 0.84); } callback(dataUrl); }; img.src = ev.target.result; }; reader.readAsDataURL(file); } function resizeSquareImage(file, size, callback) { var reader = new FileReader(); reader.onload = function (ev) { var img = new Image(); img.onload = function () { 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); } callback(dataUrl); }; img.src = ev.target.result; }; reader.readAsDataURL(file); } document.addEventListener('change', function (e) { if (!e.target) return; if (e.target.id === 'background-file-input') { var bgFile = e.target.files && e.target.files[0]; if (!bgFile) return; resizeCoverImage(bgFile, 1600, 700, function (dataUrl) { var hidden = document.getElementById('background-data-input'); var remove = document.getElementById('remove-background-input'); var preview = document.getElementById('background-preview'); if (hidden) hidden.value = dataUrl; if (remove) remove.value = 'false'; if (preview) { preview.classList.remove('is-empty'); preview.innerHTML = ''; } }); return; } if (e.target.id === 'repo-background-file-input') { var repoBgFile = e.target.files && e.target.files[0]; if (!repoBgFile) return; resizeCoverImage(repoBgFile, 1600, 700, function (dataUrl) { var hidden = document.getElementById('repo-background-data-input'); var remove = document.getElementById('remove-repo-background-input'); var preview = document.getElementById('repo-background-preview'); if (hidden) hidden.value = dataUrl; if (remove) remove.value = 'false'; if (preview) { preview.classList.remove('is-empty'); preview.innerHTML = ''; } }); return; } if (e.target.id === 'repo-avatar-file-input') { var repoFile = e.target.files && e.target.files[0]; if (!repoFile) return; resizeSquareImage(repoFile, 256, function (dataUrl) { var hidden = document.getElementById('repo-avatar-data-input'); var remove = document.getElementById('remove-repo-avatar-input'); var preview = document.getElementById('repo-avatar-preview-wrap'); if (hidden) hidden.value = dataUrl; if (remove) remove.value = 'false'; if (preview) { preview.innerHTML = 'Repository avatar'; } }); } }); document.addEventListener('click', function (e) { var removeBg = e.target.closest('#remove-background-btn'); if (removeBg) { e.preventDefault(); var hidden = document.getElementById('background-data-input'); var remove = document.getElementById('remove-background-input'); var fileInput = document.getElementById('background-file-input'); var preview = document.getElementById('background-preview'); if (hidden) hidden.value = ''; if (remove) remove.value = 'true'; if (fileInput) fileInput.value = ''; if (preview) { preview.classList.add('is-empty'); preview.innerHTML = ''; } removeBg.style.display = 'none'; return; } var removeRepoBg = e.target.closest('#remove-repo-background-btn'); if (removeRepoBg) { e.preventDefault(); var hidden = document.getElementById('repo-background-data-input'); var remove = document.getElementById('remove-repo-background-input'); var fileInput = document.getElementById('repo-background-file-input'); var preview = document.getElementById('repo-background-preview'); if (hidden) hidden.value = ''; if (remove) remove.value = 'true'; if (fileInput) fileInput.value = ''; if (preview) { preview.classList.add('is-empty'); preview.innerHTML = ''; } removeRepoBg.style.display = 'none'; return; } var removeRepoAvatar = e.target.closest('#remove-repo-avatar-btn'); if (removeRepoAvatar) { e.preventDefault(); var hidden = document.getElementById('repo-avatar-data-input'); var remove = document.getElementById('remove-repo-avatar-input'); var fileInput = document.getElementById('repo-avatar-file-input'); var preview = document.getElementById('repo-avatar-preview-wrap'); if (hidden) hidden.value = ''; if (remove) remove.value = 'true'; if (fileInput) fileInput.value = ''; if (preview) preview.innerHTML = '?'; removeRepoAvatar.style.display = 'none'; return; } var deleteUser = e.target.closest('[data-admin-delete-user]'); if (deleteUser) { e.preventDefault(); var name = deleteUser.getAttribute('data-admin-delete-name') || ''; var userForm = document.getElementById('admin-delete-user-form'); var userInput = document.getElementById('admin_delete_confirm_user'); var userTarget = document.getElementById('admin-delete-user-target'); var userSubmit = document.getElementById('admin-delete-user-submit'); if (userForm) userForm.setAttribute('action', '/admin/users/' + deleteUser.getAttribute('data-admin-delete-user') + '/delete'); if (userTarget) userTarget.textContent = '@' + name; if (userInput) { userInput.value = ''; userInput.setAttribute('data-confirm-input', name); } if (userSubmit) userSubmit.disabled = true; openDialog('admin-delete-user-dialog'); return; } var deleteRepo = e.target.closest('[data-admin-delete-repo]'); if (deleteRepo) { e.preventDefault(); var repoName = deleteRepo.getAttribute('data-admin-delete-name') || ''; var repoForm = document.getElementById('admin-delete-repo-form'); var repoInput = document.getElementById('admin_delete_confirm_repo'); var repoTarget = document.getElementById('admin-delete-repo-target'); var repoSubmit = document.getElementById('admin-delete-repo-submit'); if (repoForm) repoForm.setAttribute('action', '/admin/repos/' + deleteRepo.getAttribute('data-admin-delete-repo') + '/delete'); if (repoTarget) repoTarget.textContent = repoName; if (repoInput) { repoInput.value = ''; repoInput.setAttribute('data-confirm-input', repoName); } if (repoSubmit) repoSubmit.disabled = true; openDialog('admin-delete-repo-dialog'); } }); // 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(); })();