(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 = '
';
}
};
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 = '
';
}
});
}
});
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();
})();