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

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
173 lines · 6.6 KB Raw History
<script lang="ts">
	import RepoList from '#lib/organisms/RepoList.svelte';
	import OwnerCards from '#lib/organisms/OwnerCards.svelte';
	import CodeResults from '#lib/organisms/CodeResults.svelte';
	import Meta from '#lib/atoms/Meta.svelte';
	import Icon from '#lib/atoms/Icon.svelte';
	import Count from '#lib/atoms/Count.svelte';
	import Empty from '#lib/molecules/Empty.svelte';
	import More from '#lib/atoms/More.svelte';
	import { Pages, type Kept } from '#lib/pages.svelte.ts';
	import type { FileView } from '#lib/lexicons/dev/knotless/searchCode.ts';
	import type { Snapshot } from './$types';
	import { plural, repoHref } from '#lib/format.ts';
	let { data } = $props();
	const q = $derived(data.query);
	const topic = $derived(data.topic);
	const code = $derived(data.code);
	const heading = $derived(
		topic
			? `Repositories about ${topic}`
			: q
				? `Results for “${q}”`
				: data.type === 'code'
					? 'Code'
					: 'Repositories'
	);
	const title = $derived(
		topic
			? `${topic} · knotless`
			: q
				? `“${q}” · knotless`
				: data.type === 'code'
					? 'code · knotless'
					: 'repositories · knotless'
	);

	/** This search, as `type`. */
	const of = (type: 'code' | 'repositories', query = q) => {
		const params = new URLSearchParams();
		if (query) params.set('q', query);
		if (topic && type === 'repositories') params.set('topic', topic);
		if (type === 'code') params.set('type', 'code');
		return `/search${params.size ? `?${params}` : ''}`;
	};

	// The repository `repo:` keeps the search to, and the search without it.
	const within = $derived(/(?:^|\s)repo:(\S+?)\/(\S+)(?=\s|$)/.exec(q));
	const everywhere = $derived(of('code', q.replace(/(^|\s)repo:\S+/, ' ').trim()));

	// The language `lang:` keeps the search to, and the search without it.
	const LANG = /(?:^|\s)lang:("[^"]*"|\S+)/;
	const picked = $derived(LANG.exec(q)?.[1].replace(/"/g, '').toLowerCase());
	const unpicked = $derived(q.replace(LANG, ' ').replace(/\s+/g, ' ').trim());

	// The languages of all the search finds, most files first, each to
	// narrow it to, or, picked, to widen it again: each row filled behind
	// as far as its share of the first's, which fills its own.
	const languages = $derived(code.kind === 'found' ? (code.answer.languages ?? []) : []);
	const most = $derived(languages[0]?.files ?? 1);
	// The files found, a page at a time, each added below those before.
	const files = new Pages(
		() => data,
		(d) =>
			d.code.kind === 'found'
				? { items: d.code.answer.files, cursor: d.code.answer.cursor }
				: { items: [] },
		(f) => `${f.repo.did}/${f.repo.name}/${f.path}`
	);
	// The files loaded below the first page, kept for going back to it.
	export const snapshot: Snapshot<Kept<FileView> | null> = {
		capture: () => files.capture(),
		restore: (kept) => files.restore(kept)
	};
	// How deep the pages go, as searchCode stops them.
	const DEEPEST = 200;
	const narrowed = (name: string) =>
		name.toLowerCase() === picked
			? of('code', unpicked)
			: of('code', `${unpicked} lang:${name.includes(' ') ? `"${name}"` : name}`);
</script>

<Meta {title} />

<div class="search-page results">
	<div class="search-side">
		<nav class="account-nav search-nav" aria-label="Filter by">
			<p class="search-nav-head">Filter by</p>
			<a href={of('code')} aria-current={data.type === 'code' ? 'page' : undefined}
				><Icon name="code" />Code{#if data.counts.code !== undefined}<Count
						n={data.counts.code}
					/>{/if}</a
			>
			<a href={of('repositories')} aria-current={data.type === 'repositories' ? 'page' : undefined}
				><Icon name="repo" />Repositories{#if data.counts.repos !== undefined}<Count
						n={data.counts.repos}
						more={data.counts.more}
					/>{/if}</a
			>
		</nav>
		{#if languages.length}
			<nav class="search-langs" aria-labelledby="search-langs-head">
				<p class="search-nav-head" id="search-langs-head">Languages</p>
				{#each languages as l (l.name)}<a
						class="search-lang"
						href={narrowed(l.name)}
						aria-current={l.name.toLowerCase() === picked ? 'page' : undefined}
						title={plural(l.files, 'file', 'files')}
						aria-label="{l.name}, {plural(l.files, 'file', 'files')}"
						style:--lang={l.color ?? 'var(--faint)'}
						><i
							class="search-lang-bar"
							aria-hidden="true"
							style:width="max(var(--space-3), {(l.files * 100) / most}%)"
						></i><span>{l.name}</span></a
					>{/each}
			</nav>
		{/if}
	</div>
	<div class="settings-main">
		<h1>{heading}</h1>
		{#if data.type === 'code'}
			{#if code.kind === 'found' && code.answer.files.length}
				<p class="search-count mute">
					{plural(code.answer.fileCount, 'file', 'files')}{#if within}{' in '}<a
							href={repoHref(within[1], within[2])}>{within[1]}/{within[2]}</a
						>{' · '}<a href={everywhere}>Search all of knotless</a>{/if}
				</p>
				<CodeResults files={files.items} />
				<More pages={files} label="More files" />
				{#if !files.href && code.answer.fileCount > DEEPEST}<p class="search-deepest mute">
						These are the best {DEEPEST}. Add lang:, file: or repo: to find the rest.
					</p>{/if}
			{:else}
				<div class="box">
					{#if code.kind === 'off'}<Empty title="Code search is off here"
							>This server searches no code.</Empty
						>
					{:else if code.kind === 'missing'}<Empty title="No repository {code.repo}"
							>Check its owner and name, or <a href={everywhere}>search all of knotless</a>.</Empty
						>
					{:else if code.kind === 'bad'}<Empty title="That search can’t be read"
							>{code.message}</Empty
						>
					{:else if code.kind === 'found'}<Empty title="No code found"
							>{#if picked && languages.length}No {languages.find(
									(l) => l.name.toLowerCase() === picked
								)?.name ?? picked} file matches.
								<a href={of('code', unpicked)}>Search every language</a> instead.{:else if within}Nothing
								in {within[1]}/{within[2]} matches. <a href={everywhere}>Search all of knotless</a> instead.{:else}Only
								each repository’s default branch is searched. <a href={of('repositories')}
									>Search repositories</a
								> instead.{/if}</Empty
						>
					{:else}<Empty title="Search code"
							>Type what to look for, as a word, "quoted text" or /a regex/. Add repo:owner/name to
							search one repository.</Empty
						>{/if}
				</div>
			{/if}
		{:else}
			{#if data.owners.length}
				<h2>Owners</h2>
				<OwnerCards owners={data.owners} />
				<h2>Repositories</h2>
			{/if}
			<RepoList repos={data.repos}
				>{#snippet empty()}{#if data.indexed}<Empty title="No repositories found" />{:else}<Empty
							title="Search is off here">This server keeps no index to search.</Empty
						>{/if}{/snippet}</RepoList
			>
		{/if}
	</div>
</div>