~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
138 lines · 5.0 KB Raw History
<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>