import type { SearchParamValue } from '../../lib/repo-utils';
export type PullStateFilter = 'open' | 'closed' | 'merged';
export type PullSourceMode = 'branch' | 'fork' | 'patch';
export interface PullFilter {
state: PullStateFilter;
author?: string;
query?: string;
}
const searchParamString = (value: SearchParamValue): string => Array.isArray(value) ? value[0] ?? '' : value ?? '';
export const parsePullFilter = (value: SearchParamValue): PullFilter => {
const filter: PullFilter = { state: 'open' };
const tokens = searchParamString(value).split(/\s+/).filter(Boolean);
const queryTokens: string[] = [];
for (const token of tokens) {
if (token === 'state:closed') {
filter.state = 'closed';
continue;
}
if (token === 'state:merged') {
filter.state = 'merged';
continue;
}
if (token === 'state:open') {
filter.state = 'open';
continue;
}
if (token.startsWith('author:')) {
const author = token.slice('author:'.length).trim();
if (author) {
filter.author = author;
}
continue;
}
queryTokens.push(token);
}
const query = queryTokens.join(' ').trim();
if (query) {
filter.query = query;
}
return filter;
};
export const parsePullStateFilter = (value: SearchParamValue): PullStateFilter => parsePullFilter(value).state;
export const buildPullFilterQuery = (filter: PullFilter): string =>
[
filter.query,
`state:${filter.state}`,
filter.author ? `author:${filter.author}` : undefined,
].filter(Boolean).join(' ');
export const parsePastedPatchPrefill = (patch: string): { title: string; body: string } => {
const subject = /^Subject:\s*(?:\[[^\]]+\]\s*)?(.*)$/im.exec(patch)?.[1]?.trim() ?? '';
const headerEnd = patch.indexOf('\n---');
const bodyStart = patch.search(/^Subject:.*$/im);
if (!subject || bodyStart === -1 || headerEnd === -1 || headerEnd <= bodyStart) {
return { title: subject, body: '' };
}
const afterSubject = patch.slice(bodyStart).replace(/^Subject:.*(?:\r?\n)?/i, '');
const separator = afterSubject.indexOf('\n---');
const body = separator === -1 ? '' : afterSubject.slice(0, separator).trim();
return { title: subject, body };
};
import type { PullComment } from '../../lib/api/pulls';
export type PRCommentThread = {
item: PullComment;
replies: PullComment[];
};
export const getRoundIndexForComment = (commentCreatedAt: string, rounds: Array<{ createdAt: string }>): number => {
if (rounds.length <= 1) return 0;
const commentTime = new Date(commentCreatedAt).getTime();
for (let i = 1; i < rounds.length; i++) {
const nextRoundTime = new Date(rounds[i].createdAt).getTime();
if (commentTime < nextRoundTime) {
return i - 1;
}
}
return rounds.length - 1;
};
export const buildPRCommentThreads = (comments: PullComment[]): PRCommentThread[] => {
const byUri = new Map(comments.map((comment) => [comment.uri, comment]));
const topLevel: PullComment[] = [];
const repliesByRoot = new Map<string, PullComment[]>();
const findRoot = (comment: PullComment): PullComment | null => {
let current = comment;
const visited = new Set<string>([comment.uri]);
while ((current.value as any).replyTo) {
const replyToRef = (current.value as any).replyTo;
const parentUri = typeof replyToRef === 'string' ? replyToRef : (replyToRef.uri || replyToRef.URI || replyToRef.Uri);
if (!parentUri) break;
const parent = byUri.get(parentUri);
if (!parent || visited.has(parent.uri)) {
return current === comment ? null : current;
}
current = parent;
visited.add(current.uri);
}
return current;
};
for (const comment of comments) {
const root = findRoot(comment);
if (!root || root.uri === comment.uri) {
topLevel.push(comment);
continue;
}
repliesByRoot.set(root.uri, [...(repliesByRoot.get(root.uri) ?? []), comment]);
}
return topLevel.map((item) => ({
item,
replies: repliesByRoot.get(item.uri) ?? [],
}));
};