@@ -3,20 +3,24 @@ import type { CollectionEntry } from "astro:content";
3
3
import Head from " @components/Head/index.astro" ;
4
4
import BaseLayout from " ./BaseLayout.astro" ;
5
5
import GridItemLibrary from " @components/GridItem/Library.astro" ;
6
+ import LibraryListing from " @components/LibraryListing/index.astro" ;
6
7
import { setJumpToState , type JumpToLink } from " ../globals/state" ;
7
8
import { getCurrentLocale , getUiTranslator } from " ../i18n/utils" ;
8
9
import { categories } from " ../content/libraries/config" ;
10
+ import Button from " @components/Button/index.astro" ;
11
+ import _ from " lodash" ;
9
12
10
13
interface Props {
11
14
entries: CollectionEntry <" libraries" >[];
12
15
title: string ;
16
+ full? : boolean ;
13
17
}
14
18
type LibraryEntry = CollectionEntry <" libraries" >;
15
19
16
20
const currentLocale = getCurrentLocale (Astro .url .pathname );
17
21
const t = await getUiTranslator (currentLocale );
18
22
19
- const { entries } = Astro .props ;
23
+ const { entries, full } = Astro .props ;
20
24
21
25
function strCompare(a : string , b : string ) {
22
26
if (a < b ) {
@@ -28,19 +32,56 @@ function strCompare(a: string, b: string) {
28
32
return 0 ;
29
33
}
30
34
31
- const sections = categories
35
+ let sections = categories
32
36
.map ((slug ) => {
33
37
const name = t (" libraryCategories" , slug );
34
- const sectionEntries = entries
38
+ let sectionEntries = entries
35
39
.filter ((e : LibraryEntry ) => e .data .category === slug )
36
40
.sort ((a : LibraryEntry , b : LibraryEntry ) =>
37
41
strCompare (a .data .name .toLowerCase (), b .data .name .toLowerCase ())
38
42
);
39
43
40
- return { slug , name , sectionEntries };
44
+
45
+ return { slug , name , sectionEntries , allEntries: sectionEntries };
41
46
})
42
47
.filter ((section ) => section .sectionEntries .length > 0 );
43
48
49
+ if (! full ) {
50
+ // On the featured libraries page, we want to show as close to 4 entries
51
+ // per section as possible, while also trying to give all contributors
52
+ // approximately equal footing of being featured. To do this, we don't
53
+ // let contributors show up >3x on the featured page, and we try a
54
+ // Monte Carlo approach to try to get as close to this as possible.
55
+ const targetEntriesPerSection = 4
56
+
57
+ let minScore = 1000
58
+ let bestSections = sections
59
+ for (let attempt = 0 ; attempt < 100 ; attempt ++ ) {
60
+ const entriesByAuthor = _ .groupBy (entries , (e : LibraryEntry ) => e .data .author [0 ].name )
61
+ const toRemove = new Set ()
62
+ for (const key in entriesByAuthor ) {
63
+ if (entriesByAuthor [key ].length > 3 ) {
64
+ for (const entry of _ .shuffle (entriesByAuthor [key ]).slice (3 )) {
65
+ toRemove .add (entry .id )
66
+ }
67
+ }
68
+ }
69
+ const candidateSections = sections .map ((s ) => ({
70
+ ... s ,
71
+ sectionEntries: s .sectionEntries .filter ((e ) => ! toRemove .has (e .id )).slice (0 , targetEntriesPerSection ),
72
+ allEntries: s .sectionEntries ,
73
+ }));
74
+ const score = candidateSections
75
+ .map ((s ) => Math .abs (s .sectionEntries .length - targetEntriesPerSection ))
76
+ .reduce ((acc , next ) => acc + next , 0 );
77
+ if (score < minScore ) {
78
+ minScore = score ;
79
+ bestSections = candidateSections ;
80
+ }
81
+ }
82
+ sections = bestSections ;
83
+ }
84
+
44
85
const pageJumpToLinks = categories .map ((category ) => ({
45
86
url: ` /libraries#${category } ` ,
46
87
label: t (" libraryCategories" , category ),
@@ -60,17 +101,33 @@ setJumpToState({
60
101
variant =" item"
61
102
topic =" community"
62
103
>
104
+
105
+ <div class =" flex" >
106
+ <Button selected ={ ! full } href =" /libraries/" >{ t (" LibrariesLayout" , " Featured" )} </Button >
107
+ <Button selected ={ full } href =" /libraries/directory/" >{ t (" LibrariesLayout" , " Everything" )} </Button >
108
+ </div >
63
109
{
64
- sections .map (({ slug , name , sectionEntries }) => (
110
+ sections .map (({ slug , name , sectionEntries , allEntries }) => (
65
111
<section >
66
112
<h2 id = { slug } >{ name } </h2 >
67
- <ul class = " content-grid-simple" >
68
- { sectionEntries .map ((entry : LibraryEntry ) => (
69
- <li >
70
- <GridItemLibrary item = { entry } />
71
- </li >
72
- ))}
73
- </ul >
113
+ { full ? (
114
+ <>
115
+ { sectionEntries .map ((entry : LibraryEntry ) => (
116
+ <LibraryListing item = { entry } />
117
+ ))}
118
+ </>
119
+ ) : (
120
+ <>
121
+ <ul class = " content-grid-simple" >
122
+ { sectionEntries .map ((entry : LibraryEntry ) => (
123
+ <li >
124
+ <GridItemLibrary item = { entry } />
125
+ </li >
126
+ ))}
127
+ </ul >
128
+ <Button href = { ` /libraries/directory/#${slug } ` } >{ t (" LibrariesLayout" , " View All" )} ({ allEntries .length } )</Button >
129
+ </>
130
+ )}
74
131
</section >
75
132
))
76
133
}
0 commit comments