// A filter of pulls and issues, in the words knotless-serve reads // (crates/knotless/src/query.rs): `label:kind/bug -label:area/web // author:alice.test crash`. The pages change it a term at a time. /** `q` split on spaces, except within double quotes, each term as written. */ export function terms(q: string): string[] { return q.match(/(?:[^\s"]+|"[^"]*"?)+/g) ?? []; } /** A term's key and its value, its quotes taken off. */ function parts(term: string): [string, string] { const at = term.indexOf(':'); return at < 0 ? ['', term] : [term.slice(0, at), term.slice(at + 1).replaceAll('"', '')]; } const stateTerm = /^(?:state|is):(?:open|closed|merged)$/; /** Whether `q` asks for the closed (or merged) ones, else the open, `state` if it says neither. */ export function closed(q: string, state?: string | null): boolean { const said = terms(q).findLast((t) => stateTerm.test(t)); return said ? !said.endsWith(':open') : state === 'closed'; } /** `q` asking for the open ones or the closed. */ export function withState(q: string, state: 'open' | 'closed'): string { return [`state:${state}`, ...terms(q).filter((t) => !stateTerm.test(t))].join(' '); } /** Whether `q` has a `key:` term of `value`. */ export function has(q: string, key: string, value: string): boolean { return terms(q).some((t) => { const [k, v] = parts(t); return k === key && v === value; }); } /** `q` with a `key:` term of `value`, as it is if it has one. */ export function withTerm(q: string, key: string, value: string): string { if (has(q, key, value)) return q; return [...terms(q), `${key}:${/\s/.test(value) ? `"${value}"` : value}`].join(' '); } /** `q` with its `key:` term of `value` taken off, or put on if it has none. */ export function toggled(q: string, key: string, value: string): string { if (!has(q, key, value)) return withTerm(q, key, value); return terms(q) .filter((t) => { const [k, v] = parts(t); return k !== key || v !== value; }) .join(' '); } /** Whether `q` filters by more than state. */ export function filtered(q: string): boolean { return terms(q).some((t) => !stateTerm.test(t)); } /** The address of the list `q` filters, from the list's own. */ export function filterHref(q: string, cursor?: string): string { const params = new URLSearchParams(); if (q.trim()) params.set('q', q.trim()); if (cursor) params.set('cursor', cursor); return `?${params}`; }