johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<script lang="ts">
import type * as getPreferences from '#lib/lexicons/dev/knotless/getPreferences.ts';
import { NOTIFICATION_PREF, notificationsOf, putPrefs } from '#lib/preferences.ts';
import { invalidateAll } from '$app/navigation';
import { onMount } from 'svelte';
import { browserState, setBrowser, type BrowserState } from '#lib/notifications.svelte.ts';
/**
* Which notifications the account hears of, as the appview keeps them:
* each reason it gives, on or off, and whether only of what accounts the
* account follows did. Each change is kept as it is made, the rest as
* they were, since the appview takes them whole.
*/
let { preferences }: { preferences: getPreferences.NotificationPref } = $props();
let kept = $derived(preferences);
let busy = $state(false);
let problem = $state('');
/** What each reason the pages know of is about; one they do not is shown by its name. */
const ABOUT: Record<string, [string, string]> = {
star: ['Stars', 'Someone stars one of your repositories.'],
follow: ['Follows', 'Someone follows you.'],
pull: ['Pulls', 'Someone opens a pull on one of your repositories.'],
issue: ['Issues', 'Someone opens an issue on one of your repositories.'],
comment: ['Comments', 'Someone comments on your pull or issue, or on one you commented on.'],
mention: ['Mentions', 'Someone mentions you in a pull, an issue or a comment, as @handle.'],
'review-requested': ['Review requests', 'Someone asks you to review a pull, once it is ready.'],
assigned: ['Assignments', 'The owner or a collaborator assigns you a pull or an issue.'],
invite: ['Invitations', 'An organization invites you to join it.'],
'checks-failed': ['Failed checks', 'Checks fail on your pull, at its latest run.'],
ejected: [
'Merge queue',
'The merge queue takes your pull out, when it conflicts or its checks fail.'
],
subscribed: ['Subscriptions', 'Someone comments on a pull or issue you subscribed to.'],
watching: ['Watching', 'Something happens on a repository you watch.']
};
// Whether this browser shows them, which only it can say.
let browser = $state<BrowserState>('off');
let asking = $state(false);
onMount(() => (browser = browserState()));
async function toggleBrowser(on: boolean) {
asking = true;
browser = await setBrowser(on);
asking = false;
}
const about = (reason: string) =>
ABOUT[reason] ?? [reason[0].toUpperCase() + reason.slice(1), ''];
async function put(change: { reason?: string; enabled?: boolean; followedOnly?: boolean }) {
busy = true;
problem = '';
const reasons = (kept.reasons ?? []).map((r) =>
r.reason === change.reason ? { ...r, enabled: !!change.enabled } : r
);
const followedOnly = change.followedOnly ?? kept.followedOnly ?? false;
try {
const all = await putPrefs([{ $type: NOTIFICATION_PREF, reasons, followedOnly }]);
kept = notificationsOf(all) ?? kept;
// The bell's count is of what is heard of now.
await invalidateAll();
} catch (err) {
problem = (err as Error).message;
} finally {
busy = false;
}
}
</script>
<div class="settings-section"><h2>Notifications</h2></div>
<div class="settings-form">
<p>Choose what you hear of, on these pages, the command line and MCP alike.</p>
<fieldset class="field">
<legend class="field-label">Notify me when</legend>
<ul class="box list toggles">
{#each kept.reasons ?? [] as r (r.reason)}
{@const [name, what] = about(r.reason)}
<li>
<label class="toggle">
<input
type="checkbox"
checked={r.enabled}
disabled={busy}
onchange={(e) => put({ reason: r.reason, enabled: e.currentTarget.checked })}
/>
<span class="grow"
><b>{name}</b>{#if what}<span class="about">{what}</span>{/if}</span
>
</label>
</li>
{/each}
</ul>
</fieldset>
<fieldset class="field">
<legend class="field-label">From</legend>
<label class="toggle"
><input
type="radio"
name="from"
checked={!kept.followedOnly}
disabled={busy}
onchange={() => put({ followedOnly: false })}
/><span>Anyone</span></label
>
<label class="toggle"
><input
type="radio"
name="from"
checked={kept.followedOnly}
disabled={busy}
onchange={() => put({ followedOnly: true })}
/><span>Only accounts I follow</span></label
>
</fieldset>
<fieldset class="field">
<legend class="field-label">This browser</legend>
{#if browser === 'unsupported'}
<p class="mute">This browser cannot show notifications.</p>
{:else if browser === 'blocked'}
<p class="mute">
This browser blocks notifications from knotless. Allow them in its site settings, then come
back here.
</p>
{:else}
<label class="toggle">
<input
type="checkbox"
checked={browser === 'on'}
disabled={asking}
onchange={(e) => toggleBrowser(e.currentTarget.checked)}
/>
<span class="grow"
><b>Show notifications</b><span class="about"
>While knotless is open in a tab, new ones show on your screen.</span
></span
>
</label>
{/if}
</fieldset>
{#if problem}<p class="problem" role="alert">{problem}</p>{/if}
</div>