alpha
Login
or
Join now
nandi.uk
/
semble
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
add bot filter to feed controls
author
Wesley Finck
date
3 months ago
(Apr 15, 2026, 11:43 AM -0700)
commit
55f6684e
55f6684ec7e7346f14537575c7d41b7e1c0ea252
parent
0aff9a83
0aff9a83c60be8b8471a8615616892a136030981
+71
-2
6 changed files
Expand all
Collapse all
Unified
Split
src
webapp
features
feeds
components
feedControls
FeedControls.tsx
containers
myFeedContainer
MyFeedContainer.tsx
lib
dal.ts
feedKeys.ts
queries
useFollowingFeed.tsx
useGlobalFeed.tsx
+45
-1
src/webapp/features/feeds/components/feedControls/FeedControls.tsx
View file
Reviewed
···
37
37
{ value: 'following' as const, label: 'Following' },
38
38
];
39
39
40
40
+
const botFilterOptions = [
41
41
+
{ value: false, label: 'Hide bots' },
42
42
+
{ value: true, label: 'Include bots' },
43
43
+
];
44
44
+
40
45
const activityTypeOptions = [
41
46
{
42
47
value: ActivityType.CARD_COLLECTED,
···
66
71
.getAll('activityTypes')
67
72
.map(paramToActivityType)
68
73
.filter((t): t is ActivityType => t !== undefined);
74
74
+
const includeKnownBotsFromUrl =
75
75
+
searchParams.get('includeKnownBots') === 'true';
69
76
70
77
const [optimisticSource, setOptimisticSource] =
71
78
useOptimistic<ActivitySource | null>(sourceFromUrl);
···
77
84
);
78
85
const [optimisticActivityTypes, setOptimisticActivityTypes] =
79
86
useOptimistic<ActivityType[]>(activityTypesFromUrl);
87
87
+
const [optimisticIncludeKnownBots, setOptimisticIncludeKnownBots] =
88
88
+
useOptimistic<boolean>(includeKnownBotsFromUrl);
80
89
81
90
const [typePopoverOpened, setTypePopoverOpened] = useState(false);
82
91
···
160
169
});
161
170
};
162
171
172
172
+
const handleBotFilterClick = (includeKnownBots: boolean) => {
173
173
+
startTransition(() => {
174
174
+
setOptimisticIncludeKnownBots(includeKnownBots);
175
175
+
176
176
+
const params = new URLSearchParams(searchParams.toString());
177
177
+
if (includeKnownBots) {
178
178
+
params.set('includeKnownBots', 'true');
179
179
+
} else {
180
180
+
params.delete('includeKnownBots');
181
181
+
}
182
182
+
183
183
+
router.push(`?${params.toString()}`, { scroll: false });
184
184
+
});
185
185
+
};
186
186
+
163
187
const hasActiveFilters =
164
188
optimisticSource !== null ||
165
189
optimisticFeed !== 'global' ||
166
190
optimisticType !== null ||
167
167
-
optimisticActivityTypes.length > 0;
191
191
+
optimisticActivityTypes.length > 0 ||
192
192
+
optimisticIncludeKnownBots !== false;
168
193
169
194
const handleClear = () => {
170
195
startTransition(() => {
···
172
197
setOptimisticFeed('global');
173
198
setOptimisticType(null);
174
199
setOptimisticActivityTypes([]);
200
200
+
setOptimisticIncludeKnownBots(false);
175
201
176
202
router.push('?', { scroll: false });
177
203
});
···
220
246
onClick={() => handleFeedClick(option.value)}
221
247
rightSection={
222
248
option.value === optimisticFeed ? <IoMdCheckmark /> : null
249
249
+
}
250
250
+
closeMenuOnClick={false}
251
251
+
>
252
252
+
{option.label}
253
253
+
</Menu.Item>
254
254
+
))}
255
255
+
</>
256
256
+
257
257
+
<>
258
258
+
<Menu.Label>Accounts</Menu.Label>
259
259
+
{botFilterOptions.map((option) => (
260
260
+
<Menu.Item
261
261
+
key={String(option.value)}
262
262
+
onClick={() => handleBotFilterClick(option.value)}
263
263
+
rightSection={
264
264
+
option.value === optimisticIncludeKnownBots ? (
265
265
+
<IoMdCheckmark />
266
266
+
) : null
223
267
}
224
268
closeMenuOnClick={false}
225
269
>
+3
src/webapp/features/feeds/containers/myFeedContainer/MyFeedContainer.tsx
View file
Reviewed
···
28
28
const selectedSource = searchParams.get('source') as ActivitySource;
29
29
const selectedFeed =
30
30
(searchParams.get('feed') as 'global' | 'following') || 'global';
31
31
+
const includeKnownBots = searchParams.get('includeKnownBots') === 'true';
31
32
32
33
// Parse activityTypes from URL params (lowercase) and convert to enum values
33
34
const activityTypesParam = searchParams.getAll('activityTypes');
···
48
49
urlType: selectedUrlType,
49
50
source: selectedSource,
50
51
activityTypes: activityTypesFilter,
52
52
+
includeKnownBots,
51
53
});
52
54
const followingFeed = useFollowingFeed({
53
55
urlType: selectedUrlType,
54
56
source: selectedSource,
55
57
activityTypes: activityTypesFilter,
58
58
+
includeKnownBots,
56
59
enabled: selectedFeed === 'following',
57
60
});
58
61
+3
src/webapp/features/feeds/lib/dal.ts
View file
Reviewed
···
9
9
urlType?: UrlType;
10
10
source?: ActivitySource;
11
11
activityTypes?: ActivityType[];
12
12
+
includeKnownBots?: boolean;
12
13
}
13
14
14
15
export const getGlobalFeed = cache(async (params?: PageParams) => {
···
19
20
urlType: params?.urlType,
20
21
source: params?.source,
21
22
activityTypes: params?.activityTypes,
23
23
+
includeKnownBots: params?.includeKnownBots,
22
24
});
23
25
24
26
return response;
···
48
50
urlType: params?.urlType,
49
51
source: params?.source,
50
52
activityTypes: params?.activityTypes,
53
53
+
includeKnownBots: params?.includeKnownBots,
51
54
});
52
55
53
56
return response;
+14
-1
src/webapp/features/feeds/lib/feedKeys.ts
View file
Reviewed
···
7
7
urlType?: UrlType,
8
8
source?: ActivitySource,
9
9
activityTypes?: ActivityType[],
10
10
-
) => [...feedKeys.all(), 'infinite', limit, urlType, source, activityTypes],
10
10
+
includeKnownBots?: boolean,
11
11
+
) => [
12
12
+
...feedKeys.all(),
13
13
+
'infinite',
14
14
+
limit,
15
15
+
urlType,
16
16
+
source,
17
17
+
activityTypes,
18
18
+
includeKnownBots,
19
19
+
],
11
20
gems: () => [...feedKeys.all(), 'gems'] as const,
12
21
gemsInfinite: (
13
22
limit?: number,
14
23
urlType?: UrlType,
15
24
source?: ActivitySource,
16
25
activityTypes?: ActivityType[],
26
26
+
includeKnownBots?: boolean,
17
27
) => [
18
28
...feedKeys.gems(),
19
29
[...feedKeys.infinite()],
···
21
31
limit,
22
32
source,
23
33
activityTypes,
34
34
+
includeKnownBots,
24
35
],
25
36
following: () => [...feedKeys.all(), 'following'] as const,
26
37
followingInfinite: (
···
28
39
urlType?: UrlType,
29
40
source?: ActivitySource,
30
41
activityTypes?: ActivityType[],
42
42
+
includeKnownBots?: boolean,
31
43
) =>
32
44
[
33
45
...feedKeys.following(),
···
36
48
urlType,
37
49
source,
38
50
activityTypes,
51
51
+
includeKnownBots,
39
52
] as const,
40
53
};
+3
src/webapp/features/feeds/lib/queries/useFollowingFeed.tsx
View file
Reviewed
···
8
8
urlType?: UrlType;
9
9
source?: ActivitySource;
10
10
activityTypes?: ActivityType[];
11
11
+
includeKnownBots?: boolean;
11
12
enabled?: boolean;
12
13
}
13
14
···
21
22
props?.urlType,
22
23
props?.source,
23
24
props?.activityTypes,
25
25
+
props?.includeKnownBots,
24
26
),
25
27
staleTime: 10000,
26
28
initialPageParam: 1,
···
33
35
urlType: props?.urlType,
34
36
source: props?.source,
35
37
activityTypes: props?.activityTypes,
38
38
+
includeKnownBots: props?.includeKnownBots,
36
39
});
37
40
},
38
41
getNextPageParam: (lastPage) => {
+3
src/webapp/features/feeds/lib/queries/useGlobalFeed.tsx
View file
Reviewed
···
8
8
urlType?: UrlType;
9
9
source?: ActivitySource;
10
10
activityTypes?: ActivityType[];
11
11
+
includeKnownBots?: boolean;
11
12
}
12
13
13
14
export default function useGlobalFeed(props?: Props) {
···
19
20
props?.urlType,
20
21
props?.source,
21
22
props?.activityTypes,
23
23
+
props?.includeKnownBots,
22
24
),
23
25
staleTime: 10000,
24
26
initialPageParam: 1,
···
30
32
urlType: props?.urlType,
31
33
source: props?.source,
32
34
activityTypes: props?.activityTypes,
35
35
+
includeKnownBots: props?.includeKnownBots,
33
36
});
34
37
},
35
38
getNextPageParam: (lastPage) => {