import clsx from 'clsx'; import { Bell, BookMarked, LogOut, Search, Settings, UserRound, Plus, LineSquiggle, BookPlus } from 'lucide-solid'; import { A, useLocation, useNavigate } from '@solidjs/router'; import { createQuery } from './lib/atproto-query'; import { For, Show, createEffect, createSignal, onCleanup, onMount, type Component, type JSX } from 'solid-js'; import { Avatar, buttonStyles, inputStyles } from './components/common'; import { resolveActor } from './lib/api/identity'; import { useAuth } from './lib/auth'; import { useLiveEvents, type LiveEvent } from './lib/live-events'; import { formatRelativeTime } from './lib/repo-utils'; const TOPBAR_MENU_SELECTOR = 'details[data-topbar-menu]'; const closeOpenTopbarMenus = (root: HTMLElement, target: Node) => { root.querySelectorAll(TOPBAR_MENU_SELECTOR).forEach((menu) => { if (menu.open && !menu.contains(target)) { menu.open = false; } }); }; const eventLabel = (event: LiveEvent) => { if (event.source === 'sh.tangled.repo.issue:repo') { return `new issue on ${event.subject}`; } if (event.source === 'sh.tangled.repo.pull:target.repo') { return `new pull on ${event.subject}`; } return `${event.source} -> ${event.subject}`; }; const LoginMenu: Component = () => { const auth = useAuth(); const [identifier, setIdentifier] = createSignal(''); const onSignIn = async (event: SubmitEvent) => { event.preventDefault(); await auth.signIn(identifier()); }; return (
login
setIdentifier(event.currentTarget.value)} placeholder="handle or did" class={inputStyles()} />

{auth.error()}

); }; const NotificationsMenu: Component = () => { const live = useLiveEvents(); return (
live.markRead()} >
0}> {Math.min(live.unread(), 99)}
live activity
0} fallback={
No live events yet.
} > {(event) => (
{eventLabel(event)}
{formatRelativeTime(event.receivedAt)}
)}
); }; const UserProfileMenu: Component = () => { const auth = useAuth(); const viewerQuery = createQuery(() => ({ queryKey: ['viewer', auth.currentDid()], enabled: Boolean(auth.currentDid()), queryFn: async () => resolveActor(auth.currentDid()!), })); const closeMenu = (e: MouseEvent) => { const details = (e.currentTarget as HTMLElement).closest('details'); if (details) details.open = false; }; return (
{/* */}
{(handle) => ( <> profile repositories strings )} settings
); }; const NewMenu: Component = () => { const closeMenu = (e: MouseEvent) => { const details = (e.currentTarget as HTMLElement).closest('details'); if (details) details.open = false; }; return (
); }; const TopbarSearch: Component = () => { const navigate = useNavigate(); const location = useLocation(); const [query, setQuery] = createSignal(''); let inputRef: HTMLInputElement | undefined; onMount(() => { const handler = (e: KeyboardEvent) => { if (e.ctrlKey || e.metaKey) return; if (e.key === "/") { const active = document.activeElement; if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || (active as HTMLElement).isContentEditable)) { return; } e.preventDefault(); inputRef?.focus(); } else if (e.key === "Escape") { setQuery(''); inputRef?.blur(); } } window.addEventListener("keydown", handler) onCleanup(() => window.removeEventListener("keydown", handler)) }); createEffect(() => { if (location.pathname !== '/search') { setQuery(''); return; } const params = new URLSearchParams(location.search); setQuery(params.get('q') ?? ''); }); const onSubmit = (event: SubmitEvent) => { event.preventDefault(); const trimmed = query().trim(); navigate(trimmed ? `/search?q=${encodeURIComponent(trimmed)}` : '/search'); }; return ( ); }; const Topbar: Component = () => { const auth = useAuth(); let topbar: HTMLElement | undefined; onMount(() => { const closeMenusForOutsideClick = (event: PointerEvent) => { const target = event.target; if (!topbar || !(target instanceof Node)) { return; } closeOpenTopbarMenus(topbar, target); }; document.addEventListener('pointerdown', closeMenusForOutsideClick); onCleanup(() => { document.removeEventListener('pointerdown', closeMenusForOutsideClick); }); }); return ( ); }; export const RootShell: Component<{ children?: JSX.Element }> = (props) => { const location = useLocation(); const isRaw = () => location.pathname.endsWith('/raw'); return ( {props.children}}>
{props.children}
); };