import { getIssueRecord } from '../lib/api/issues';
import { getPullRecord } from '../lib/api/pulls';
import { getRepoByDid } from '../lib/api/repos';
import {
ISSUE_COLLECTION,
} from '../lib/api/constants';
import {
parseAtUri,
} from '../lib/api/records';
import {
searchRecords,
type SearchCollection,
type SearchHit,
} from '../lib/api/search';
{ kind: 'all', label: 'all' },
{ kind: 'repos', label: 'repos', nsid: 'sh.tangled.repo' },
{ kind: 'issues', label: 'issues', nsid: 'sh.tangled.repo.issue' },
{ kind: 'pulls', label: 'pulls', nsid: 'sh.tangled.repo.pull' },
{ kind: 'issue-comments', label: 'issue comments', nsid: 'sh.tangled.repo.issue.comment' },
{ kind: 'pull-comments', label: 'pull comments', nsid: 'sh.tangled.repo.pull.comment' },
{ kind: 'issue-comments', label: 'issue comments', nsid: 'sh.tangled.feed.comment' },
{ kind: 'pull-comments', label: 'pull comments', nsid: 'sh.tangled.feed.comment' },
{ kind: 'profiles', label: 'profiles', nsid: 'sh.tangled.actor.profile' },
{ kind: 'strings', label: 'strings', nsid: 'sh.tangled.string' },
{ kind: 'labels', label: 'labels', nsid: 'sh.tangled.label.definition' },
];
return `${Math.round(milliseconds)}ms`;
};
const snippet = (value?: string, max = 220): string => {
const normalized = value?.replace(/\s+/g, ' ').trim() ?? '';
if (typeof value !== 'string') return '';
const normalized = value.replace(/\s+/g, ' ').trim();
if (normalized.length <= max) {
return normalized;
}
return `${normalized.slice(0, max - 1).trimEnd()}...`;
</Match>
<Match when={props.hit.nsid === 'sh.tangled.repo.pull'}>
<GitPullRequest class="w-4 h-4 mr-1.5 shrink-0" />
</Match>
<Match when={props.hit.nsid === 'sh.tangled.repo.issue.comment' || props.hit.nsid === 'sh.tangled.repo.pull.comment'}>
<Match when={props.hit.nsid === 'sh.tangled.repo.issue.comment' || props.hit.nsid === 'sh.tangled.repo.pull.comment' || props.hit.nsid === 'sh.tangled.feed.comment'}>
<MessageSquare class="w-4 h-4 mr-1.5 shrink-0" />
</Match>
<Match when={props.hit.nsid === 'sh.tangled.actor.profile'}>
<UserRound class="w-4 h-4 mr-1.5 shrink-0" />
);
};
const CommentParentLink: Component<{ hit: SearchHit }> = (props) => {
const isIssueComment = createMemo(() => props.hit.nsid === 'sh.tangled.repo.issue.comment');
const parentUri = createMemo(() => {
if (isIssueComment()) {
return (props.hit.value as { issue: ResourceUri }).issue;
const parentInfo = createMemo(() => {
const value = props.hit.value as any;
let uri: ResourceUri | undefined;
if (props.hit.nsid === 'sh.tangled.repo.issue.comment') {
uri = value.issue;
} else if (props.hit.nsid === 'sh.tangled.repo.pull.comment') {
uri = value.pull;
} else if (props.hit.nsid === 'sh.tangled.feed.comment') {
uri = value.subject?.uri;
}
return (props.hit.value as { pull: ResourceUri }).pull;
if (!uri) return undefined;
try {
const parsed = parseAtUri(uri);
return {
uri,
kind: parsed.collection === ISSUE_COLLECTION ? 'issue' as const : 'pull' as const,
};
} catch {
return undefined;
}
});
const parentQuery = createQuery(() => ({
queryKey: ['search-parent', props.hit.nsid, parentUri()],
enabled: Boolean(parentUri()),
queryFn: async () => isIssueComment()
? { kind: 'issue' as const, record: await getIssueRecord(parentUri()!) }
: { kind: 'pull' as const, record: await getPullRecord(parentUri()!) },
queryKey: ['search-parent', props.hit.nsid, parentInfo()?.uri],
enabled: Boolean(parentInfo()?.uri),
queryFn: async () => {
const info = parentInfo()!;
return info.kind === 'issue'
? { kind: 'issue' as const, record: await getIssueRecord(info.uri) }
: { kind: 'pull' as const, record: await getPullRecord(info.uri) };
},
staleTime: 300_000,
}));
return (
return (props.hit.value as { target: { repo: Did } }).target.repo;
}
return undefined;
});
const isIssueComment = createMemo(() => props.hit.nsid === 'sh.tangled.repo.issue.comment');
const isRepoComment = createMemo(() => isIssueComment() || props.hit.nsid === 'sh.tangled.repo.pull.comment');
const isRepoComment = createMemo(() =>
props.hit.nsid === 'sh.tangled.repo.issue.comment' ||
props.hit.nsid === 'sh.tangled.repo.pull.comment' ||
props.hit.nsid === 'sh.tangled.feed.comment'
);
const parentUri = createMemo(() => {
if (!isRepoComment()) return undefined;
if (isIssueComment()) {
return (props.hit.value as { issue: ResourceUri }).issue;
const value = props.hit.value as any;
if (props.hit.nsid === 'sh.tangled.repo.issue.comment') {
return value.issue;
}
return (props.hit.value as { pull: ResourceUri }).pull;
if (props.hit.nsid === 'sh.tangled.repo.pull.comment') {
return value.pull;
}
if (props.hit.nsid === 'sh.tangled.feed.comment') {
return value.subject?.uri;
}
return undefined;
});
const parentQuery = createQuery(() => ({
queryKey: ['search-parent', props.hit.nsid, parentUri()],
const parentRepoDidQuery = createQuery(() => ({
queryKey: ['search-parent-repo', parentUri()],
enabled: Boolean(parentUri()),
queryFn: async () => isIssueComment()
? { kind: 'issue' as const, record: await getIssueRecord(parentUri()!) }
: { kind: 'pull' as const, record: await getPullRecord(parentUri()!) },
queryFn: async () => {
const uri = parentUri()!;
const parsed = parseAtUri(uri);
if (parsed.collection === ISSUE_COLLECTION) {
const issue = await getIssueRecord(uri);
return (issue.value as { repo: Did }).repo;
}
const pull = await getPullRecord(uri);
return (pull.value as { target: { repo: Did } }).target.repo;
},
staleTime: 300_000,
}));
const commentRepoDid = createMemo(() => {
const parent = parentQuery.data;
if (!parent) return undefined;
return parent.kind === 'issue'
? (parent.record.value as { repo: Did }).repo
: (parent.record.value as { target: { repo: Did } }).target.repo;
});
const repoDid = createMemo(() => directRepoDid() ?? commentRepoDid());
return (
<Show when={repoDid()}>
<Show when={directRepoDid() || parentRepoDidQuery.data}>
{(did) => <RepoContextLink repoDid={did()} />}
</Show>
);
};
preferredHandle?: string;
};
return value.title || value.name || value.filename || value.preferredHandle || props.hit.rkey;
});
const repoName = createMemo(() => (props.hit.value as { name?: string }).name?.trim() || props.hit.rkey);
const repoName = createMemo(() => {
const name = (props.hit.value as { name?: string }).name;
return (typeof name === 'string' ? name.trim() : null) || props.hit.rkey;
});
return (
<div class="flex items-center min-w-0 flex-1 mr-2">
<SearchResultIcon hit={props.hit} />
<A href={`/${props.hit.author.handle}`} class="truncate min-w-0">
{props.hit.author.handle}
</A>
</Match>
<Match when={props.hit.nsid === 'sh.tangled.repo.issue.comment' || props.hit.nsid === 'sh.tangled.repo.pull.comment'}>
<Match when={props.hit.nsid === 'sh.tangled.repo.issue.comment' || props.hit.nsid === 'sh.tangled.repo.pull.comment' || props.hit.nsid === 'sh.tangled.feed.comment'}>
<CommentParentLink hit={props.hit} />
</Match>
</Switch>
</div>
const SearchResultCard: Component<{ hit: SearchHit }> = (props) => {
const createdAt = createMemo(() => (props.hit.value as { createdAt?: string }).createdAt);
const body = createMemo(() => {
const value = props.hit.value as {
body?: string;
description?: string;
contents?: string;
location?: string;
pronouns?: string;
};
return snippet(value.body || value.description || value.contents || [value.location, value.pronouns].filter(Boolean).join(' '));
const value = props.hit.value as any;
const text = typeof value.body === 'string'
? value.body
: (value.body?.text ?? value.description ?? value.contents ?? [value.location, value.pronouns].filter(Boolean).join(' '));
return snippet(text);
});
const topics = createMemo(() => props.hit.nsid === 'sh.tangled.repo'
? (props.hit.value as { topics?: string[] }).topics ?? []
: []);
261 unmodified lines
Expand all