Skip to content

Commit fc55aa2

Browse files
authored
Merge pull request #28 from debba/feat/exclude-authors
feat: exclude authors from issues and pull requests
2 parents e526a5e + 2886db8 commit fc55aa2

13 files changed

Lines changed: 207 additions & 10 deletions

File tree

‎src/App.tsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ const defaultIssueFilters = (): IssueFilters => ({
117117
repos: new Set(),
118118
labels: new Set(),
119119
authors: new Set(),
120+
excludedAuthors: new Set(),
121+
authorMode: "include",
120122
assignees: new Set(),
121123
dates: { cf: "", ct: "", uf: "", ut: "" },
122124
preset: "",
@@ -128,6 +130,8 @@ const defaultPrFilters = (): PullRequestFilters => ({
128130
repos: new Set(),
129131
labels: new Set(),
130132
authors: new Set(),
133+
excludedAuthors: new Set(),
134+
authorMode: "include",
131135
assignees: new Set(),
132136
dates: { cf: "", ct: "", uf: "", ut: "" },
133137
preset: "",

‎src/components/SidebarControls.tsx‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ export function SidebarControls({
185185
const activeFacets: IssueLikeFacets = prMode ? prFacets : issueFacets;
186186
const orgSelection = ticketMode ? activeFilters.orgs : repoFilters.orgs;
187187
const orgEntries = ticketMode ? activeFacets.orgs : repoFacets.orgs;
188+
const activeAuthorSelection = activeFilters.authorMode === "include" ? activeFilters.authors : activeFilters.excludedAuthors;
188189
const onActiveFiltersChange = (next: IssueFilters | PullRequestFilters) => {
189190
if (prMode) onPrFiltersChange(next as PullRequestFilters);
190191
else onIssueFiltersChange(next as IssueFilters);
@@ -273,8 +274,40 @@ export function SidebarControls({
273274
<FilterSection title={t("sidebar.labels")} activeCount={activeFilters.labels.size} dataFor={prMode ? "prs-only" : "issues-only"} onClear={() => onActiveFiltersChange({ ...activeFilters, labels: new Set() })}>
274275
<CheckList entries={[...activeFacets.labels.entries()]} selected={activeFilters.labels} showSwatch onToggle={(value) => onActiveFiltersChange({ ...activeFilters, labels: toggleSetValue(activeFilters.labels, value) })} />
275276
</FilterSection>
276-
<FilterSection title={t("sidebar.authors")} activeCount={activeFilters.authors.size} dataFor={prMode ? "prs-only" : "issues-only"} onClear={() => onActiveFiltersChange({ ...activeFilters, authors: new Set() })}>
277-
<CheckList entries={[...activeFacets.authors.entries()]} selected={activeFilters.authors} onToggle={(value) => onActiveFiltersChange({ ...activeFilters, authors: toggleSetValue(activeFilters.authors, value) })} />
277+
<FilterSection
278+
title={t("sidebar.authors")}
279+
activeCount={activeFilters.authors.size + activeFilters.excludedAuthors.size}
280+
dataFor={prMode ? "prs-only" : "issues-only"}
281+
onClear={() => onActiveFiltersChange({ ...activeFilters, authors: new Set(), excludedAuthors: new Set() })}
282+
>
283+
<div className="opt-group author-mode" role="group" aria-label={t("sidebar.authorFilterMode")}>
284+
{(["include", "exclude"] as const).map((mode) => (
285+
<button
286+
type="button"
287+
key={mode}
288+
className={activeFilters.authorMode === mode ? "active" : ""}
289+
onClick={() => onActiveFiltersChange({ ...activeFilters, authorMode: mode })}
290+
>
291+
{t(mode === "include" ? "sidebar.includeAuthors" : "sidebar.excludeAuthors")}
292+
</button>
293+
))}
294+
</div>
295+
<CheckList
296+
entries={[...activeFacets.authors.entries()]}
297+
selected={activeAuthorSelection}
298+
onToggle={(value) => {
299+
const authors = new Set(activeFilters.authors);
300+
const excludedAuthors = new Set(activeFilters.excludedAuthors);
301+
const target = activeFilters.authorMode === "include" ? authors : excludedAuthors;
302+
const opposite = activeFilters.authorMode === "include" ? excludedAuthors : authors;
303+
if (target.has(value)) target.delete(value);
304+
else {
305+
target.add(value);
306+
opposite.delete(value);
307+
}
308+
onActiveFiltersChange({ ...activeFilters, authors, excludedAuthors });
309+
}}
310+
/>
278311
</FilterSection>
279312
<FilterSection title={t("sidebar.assignees")} activeCount={activeFilters.assignees.size} dataFor={prMode ? "prs-only" : "issues-only"} onClear={() => onActiveFiltersChange({ ...activeFilters, assignees: new Set() })}>
280313
<CheckList entries={[...activeFacets.assignees.entries()]} selected={activeFilters.assignees} onToggle={(value) => onActiveFiltersChange({ ...activeFilters, assignees: toggleSetValue(activeFilters.assignees, value) })} />

‎src/i18n/de.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ export const de: Partial<Record<keyof typeof en, string>> = {
161161
"sidebar.repositories": "Repositories",
162162
"sidebar.labels": "Labels",
163163
"sidebar.authors": "Autoren",
164+
"sidebar.authorFilterMode": "Autorenfiltermodus",
165+
"sidebar.includeAuthors": "Nur einschließen",
166+
"sidebar.excludeAuthors": "Ausschließen",
164167
"sidebar.assignees": "Zugewiesene",
165168
"sidebar.languages": "Sprachen",
166169
"sidebar.visibility": "Sichtbarkeit",

‎src/i18n/en.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ export const en = {
159159
"sidebar.repositories": "Repositories",
160160
"sidebar.labels": "Labels",
161161
"sidebar.authors": "Authors",
162+
"sidebar.authorFilterMode": "Author filter mode",
163+
"sidebar.includeAuthors": "Include only",
164+
"sidebar.excludeAuthors": "Exclude",
162165
"sidebar.assignees": "Assignees",
163166
"sidebar.languages": "Languages",
164167
"sidebar.visibility": "Visibility",

‎src/i18n/es.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ export const es: Partial<Record<keyof typeof en, string>> = {
161161
"sidebar.repositories": "Repositorios",
162162
"sidebar.labels": "Etiquetas",
163163
"sidebar.authors": "Autores",
164+
"sidebar.authorFilterMode": "Modo de filtro de autores",
165+
"sidebar.includeAuthors": "Solo incluir",
166+
"sidebar.excludeAuthors": "Excluir",
164167
"sidebar.assignees": "Asignados",
165168
"sidebar.languages": "Lenguajes",
166169
"sidebar.visibility": "Visibilidad",

‎src/i18n/fr.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ export const fr: Partial<Record<keyof typeof en, string>> = {
161161
"sidebar.repositories": "Dépôts",
162162
"sidebar.labels": "Labels",
163163
"sidebar.authors": "Auteurs",
164+
"sidebar.authorFilterMode": "Mode de filtrage des auteurs",
165+
"sidebar.includeAuthors": "Inclure uniquement",
166+
"sidebar.excludeAuthors": "Exclure",
164167
"sidebar.assignees": "Assignés",
165168
"sidebar.languages": "Langages",
166169
"sidebar.visibility": "Visibilité",

‎src/i18n/it.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ export const it: Record<keyof typeof en, string> = {
161161
"sidebar.repositories": "Repository",
162162
"sidebar.labels": "Etichette",
163163
"sidebar.authors": "Autori",
164+
"sidebar.authorFilterMode": "Modalità filtro autori",
165+
"sidebar.includeAuthors": "Solo inclusi",
166+
"sidebar.excludeAuthors": "Escludi",
164167
"sidebar.assignees": "Assegnatari",
165168
"sidebar.languages": "Linguaggi",
166169
"sidebar.visibility": "Visibilità",

‎src/i18n/zh.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ export const zh: Partial<Record<keyof typeof en, string>> = {
161161
"sidebar.repositories": "仓库",
162162
"sidebar.labels": "标签",
163163
"sidebar.authors": "作者",
164+
"sidebar.authorFilterMode": "作者筛选模式",
165+
"sidebar.includeAuthors": "仅包含",
166+
"sidebar.excludeAuthors": "排除",
164167
"sidebar.assignees": "负责人",
165168
"sidebar.languages": "语言",
166169
"sidebar.visibility": "可见性",

‎src/styles/layout-sidebar.css‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156

157157
/* Radio-like option groups */
158158
.opt-group { display: flex; gap: 4px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; padding: 3px; }
159+
.opt-group.author-mode { margin-bottom: 6px; }
159160
.opt-group button {
160161
flex: 1; background: transparent; border: none; color: var(--muted); cursor: pointer;
161162
padding: 6px 8px; border-radius: 5px; font-size: 12px; font-weight: 600;

‎src/utils/dashboard.ts‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface IssueFilters {
1414
repos: Set<string>;
1515
labels: Set<string>;
1616
authors: Set<string>;
17+
excludedAuthors: Set<string>;
18+
authorMode: "include" | "exclude";
1719
assignees: Set<string>;
1820
dates: DateFilters;
1921
preset: string;
@@ -25,6 +27,8 @@ export interface PullRequestFilters {
2527
repos: Set<string>;
2628
labels: Set<string>;
2729
authors: Set<string>;
30+
excludedAuthors: Set<string>;
31+
authorMode: "include" | "exclude";
2832
assignees: Set<string>;
2933
dates: DateFilters;
3034
preset: string;
@@ -145,7 +149,9 @@ export function filterIssues(issues: GhIssue[], filters: IssueFilters, userLogin
145149
if (filters.orgs.size && !filters.orgs.has(owner)) return false;
146150
if (filters.repos.size && !filters.repos.has(issue.repository.nameWithOwner)) return false;
147151
if (filters.labels.size && ![...(issue.labels || []).map((label) => label.name)].some((label) => filters.labels.has(label))) return false;
148-
if (filters.authors.size && !filters.authors.has(issue.author?.login || "")) return false;
152+
const author = issue.author?.login || "";
153+
if (filters.authors.size && !filters.authors.has(author)) return false;
154+
if (filters.excludedAuthors.has(author)) return false;
149155
if (filters.assignees.size && ![...(issue.assignees || []).map((assignee) => assignee.login)].some((login) => filters.assignees.has(login))) return false;
150156

151157
const created = new Date(issue.createdAt).getTime();
@@ -214,7 +220,9 @@ export function filterPullRequests(prs: GhPullRequest[], filters: PullRequestFil
214220
if (filters.orgs.size && !filters.orgs.has(owner)) return false;
215221
if (filters.repos.size && !filters.repos.has(pr.repository.nameWithOwner)) return false;
216222
if (filters.labels.size && ![...(pr.labels || []).map((label) => label.name)].some((label) => filters.labels.has(label))) return false;
217-
if (filters.authors.size && !filters.authors.has(pr.author?.login || "")) return false;
223+
const author = pr.author?.login || "";
224+
if (filters.authors.size && !filters.authors.has(author)) return false;
225+
if (filters.excludedAuthors.has(author)) return false;
218226
if (filters.assignees.size && ![...(pr.assignees || []).map((assignee) => assignee.login)].some((login) => filters.assignees.has(login))) return false;
219227

220228
const created = new Date(pr.createdAt).getTime();

0 commit comments

Comments
 (0)