A project tracker for decentralized social media platforms, clients, and tools
1import Fuse from 'fuse.js';
2import type { Project, FilterState, Network } from '../types/project';
3
4export function filterAndSortProjects(
5 projects: Project[],
6 filters: FilterState
7): Project[] {
8 let filtered = projects.filter(p => p.network === filters.network);
9
10 if (filters.tags.length > 0) {
11 filtered = filtered.filter(project =>
12 filters.tags.every(tag => project.tags.includes(tag))
13 );
14 }
15
16 if (filters.query) {
17 const fuse = new Fuse(filtered, {
18 keys: [
19 { name: 'name', weight: 2 },
20 { name: 'description', weight: 1 },
21 { name: 'tags', weight: 1 },
22 { name: 'owner', weight: 0.5 },
23 { name: 'domain', weight: 0.5 },
24 { name: 'type', weight: 0.5 }
25 ],
26 threshold: 0.3,
27 includeScore: true
28 });
29
30 const searchResults = fuse.search(filters.query);
31 filtered = searchResults.map(result => result.item);
32 }
33
34 switch (filters.sort) {
35 case 'stars':
36 return [...filtered].sort((a, b) => (b.stars || 0) - (a.stars || 0));
37
38 case 'alphabetical':
39 return [...filtered].sort((a, b) => a.name.localeCompare(b.name));
40
41 default:
42 return filtered;
43 }
44}
45
46export function getTagsByNetwork(projects: Project[]): Record<Network, string[]> {
47 const tagsByNetwork: Record<Network, string[]> = {
48 atproto: [],
49 activitypub: []
50 };
51
52 // Collect all unique tags for each network
53 projects.forEach(project => {
54 project.tags.forEach(tag => {
55 if (!tagsByNetwork[project.network].includes(tag)) {
56 tagsByNetwork[project.network].push(tag);
57 }
58 });
59 });
60
61 // Sort tags alphabetically for each network
62 Object.keys(tagsByNetwork).forEach(network => {
63 tagsByNetwork[network as Network].sort();
64 });
65
66 return tagsByNetwork;
67}