johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<script lang="ts">
import type * as getViewer from '#lib/lexicons/dev/knotless/getViewer.ts';
import type { Answer } from '#lib/data.ts';
import { urlPath } from '#lib/format.ts';
import Avatar from '#lib/atoms/Avatar.svelte';
import Icon from '#lib/atoms/Icon.svelte';
import { LOGIN } from '#lib/login.ts';
import { unread } from '#lib/notifications.svelte.ts';
import LoginDialog from '#lib/organisms/LoginDialog.svelte';
import ReportDialog from '#lib/organisms/ReportDialog.svelte';
import SearchPalette from '#lib/organisms/SearchPalette.svelte';
import StartOrgDialog, { START_ORG } from '#lib/organisms/StartOrgDialog.svelte';
/** The top of every page: the way home, search, and who is looking, with how many of their notifications are new, or the way to log in where this server can. Reporting, from any page, is one dialog here. */
let { viewer }: { viewer: Answer<typeof getViewer.main> | null } = $props();
const count = $derived(unread.count);
const me = $derived(viewer?.actor);
const home = $derived(me && `/${urlPath(me.handle)}`);
let open = $state(false);
function outside(e: MouseEvent) {
if (open && !(e.target as Element).closest('.account')) open = false;
}
const others = $derived(viewer?.accounts ?? []);
// Another account this browser is logged in to, as the one looking, without logging in again.
async function switchTo(did: string) {
await fetch('/oauth/switch', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ did })
}).catch(() => null);
location.reload();
}
async function logout() {
await fetch('/oauth/logout', { method: 'POST' }).catch(() => null);
location.reload();
}
</script>
<svelte:window onclick={outside} onkeydown={(e) => e.key === 'Escape' && (open = false)} />
<header class="top">
<div class="wrap">
<a class="brand" href="/">~ knotless</a>
<SearchPalette />
<nav>
<a class="start-link" href="/start">Getting started</a>
<!-- The docs are a site of their own, served beside the app: the browser loads them, not the router. -->
<a class="start-link" href="/docs/" data-sveltekit-reload>Docs</a>
{#if me}
<a class="new-link" href="/new" aria-label="New repository" title="New repository"
><Icon name="plus" /></a
>
<a
class="bell"
href="/notifications"
aria-label={count ? `Notifications, ${count} new` : 'Notifications'}
><Icon name="bell" />{#if count}<span class="count">{count > 99 ? '99+' : count}</span
>{/if}</a
>
<details class="popover account" bind:open>
<summary aria-label="Your account"
><Avatar name={me.handle} did={me.did} src={me.avatar} /><Icon name="caret" /></summary
>
<div class="menu">
<div class="menu-head">
<Avatar name={me.handle} did={me.did} src={me.avatar} /><span>{me.handle}</span>
</div>
{#each others as other (other.did)}
<button class="menu-item switch" onclick={() => switchTo(other.did)}
><Avatar name={other.handle} did={other.did} src={other.avatar} /><span
>{other.handle}</span
></button
>
{/each}
<button class="menu-item" popovertarget={LOGIN} onclick={() => (open = false)}
><Icon name="personAdd" />Add account</button
>
<hr />
<a href={home} onclick={() => (open = false)}><Icon name="person" />Profile</a>
<a href="{home}?tab=repos" onclick={() => (open = false)}
><Icon name="repo" />Repositories</a
>
<a href="{home}?tab=stars" onclick={() => (open = false)}><Icon name="star" />Stars</a>
{#if viewer?.organization}<a href="{home}?tab=members" onclick={() => (open = false)}
><Icon name="people" />Members</a
>{/if}
<hr />
<a href="/new" onclick={() => (open = false)}><Icon name="plus" />New repository</a>
{#if !viewer?.organization}<button
class="menu-item"
popovertarget={START_ORG}
onclick={() => (open = false)}
><Icon name="organization" />Start an organization</button
>{/if}
<a href="/settings" onclick={() => (open = false)}><Icon name="gear" />Settings</a>
{#if viewer?.admin}<a href="/admin" onclick={() => (open = false)}
><Icon name="shield" />Admin</a
>{/if}
<hr />
<button class="menu-item" onclick={logout}><Icon name="signOut" />Log out</button>
</div>
</details>
{:else if viewer?.login}
<button class="btn" popovertarget={LOGIN}>Log in</button>
{/if}
</nav>
</div>
</header>
{#if viewer?.login}<LoginDialog />{/if}
{#if me}<ReportDialog />{/if}
{#if me && !viewer?.organization}<StartOrgDialog handle={me.handle} />{/if}