From 841e106e8e1e192ec7ef30d7316884d781dd834b Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Fri, 25 Jul 2025 17:27:04 +0100 Subject: [PATCH] Add trending repositories widget --- docs/configuration.md | 4 +- .../trending-repositories-widget-preview.png | Bin 0 -> 37925 bytes go.mod | 2 +- go.sum | 8 - internal/glance/searchable-node.go | 161 ++++++++++++++++++ .../templates/trending-repositories.html | 22 +++ .../glance/widget-trending-repositories.go | 113 ++++++++++++ internal/glance/widget.go | 2 + 8 files changed, 302 insertions(+), 10 deletions(-) create mode 100644 docs/images/trending-repositories-widget-preview.png create mode 100644 internal/glance/searchable-node.go create mode 100644 internal/glance/templates/trending-repositories.html create mode 100644 internal/glance/widget-trending-repositories.go diff --git a/docs/configuration.md b/docs/configuration.md index 70e76de..a2170cd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -34,6 +34,8 @@ - [DNS Stats](#dns-stats) - [Server Stats](#server-stats) - [Repository](#repository) + + - [Trending Repositories](#trending-repositories) - [Bookmarks](#bookmarks) - [Calendar](#calendar) - [Calendar (legacy)](#calendar-legacy) @@ -907,7 +909,7 @@ https://www.youtube.com...&list={ID}&... The maximum number of videos to show. ##### `sort-by` -Used to specify the order in which the videos should get returned. Possible values are `none`, `updated`, and `posted`. +Used to specify the order in which the videos should get returned. Possible values are `none`, `updated`, and `posted`. Default value is `posted`. ##### `collapse-after` diff --git a/docs/images/trending-repositories-widget-preview.png b/docs/images/trending-repositories-widget-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..3760b837fd47cdc1e5ad51e11031cc88a9cea829 GIT binary patch literal 37925 zcmcF~1yCJLyXGMT2p-%axVw9Bhv0H>cXti$?ykYz-45;!L4#XxhXX9%fB(C+wYybY zx9aXx&2;xWJ>Bn2SKISE6aGV90uc@u4gdfkN=b?;0RRwM0Kg~WFP}avKLz%?KLQ9R zB?)0b^%VZ`N8+=YkemVdC4I#IIX-=q`{`dfmAJV+V*h@MnThG2yX{0=B2Q5a;Q&Cw zf|RI`io5PvHjFm9+1vZD&MHG~?ShGB#4SS?CyJLuw0I$PMw+GU)cC8@(>_Db-t+#o zjN!zfJDIqV{9s7LN&w=I_)Rafvhn^;9gR9~$~nJ|&Xvd@u3mg6;Z1YWJys{Yb6Fa4 zFZhg%SP?$z2ALlT0ssp9`bYr4_n+J!7Knq7%jdL@m;3tv!^m1*G@)~M;sin0NL+Hz zFDPFW{hJ)`$abs~)32@)eydgkd_t>hG<7`ke5JSih<)(1<0WJL+2X~>uK7=XPNpEN z_4|FMl}lk^!aZzx;=30^p{BI0Bi8!?H=~bZa>4b9VE6RUdtwRGn1YW71&m+<*Tf&B zcv^hK%n>sVPbcR^^SSQ(>%GT!(?+FuY9R}uFnoqO643sF_Uq;69B`Utd<4)bQ9hfR zOaUjmJuTN)j1VJaHrgjg2hY+hPl^aA{yuq8MVK8%b-K-i<6rK|xpgC7=#aD@T=hsAavtJt68=diJPv z%!{szl(edy6)qDd?DlC8s_jAISI#iy+)$WumfXS}I}hPrGm=4jb01;UYEl3+f{~}~ zIzc%w1=|g*rgimGP9rE*=rwRhzMd^YQR^#7XfwYc5s!neZk*SkH*7rkNEr^7?M#@Y z^R9eOrh}<{e3g^rc4WZ`SHoSUIZ(5$DbSrp?NnFWX3Ob)cvsyv8F>Xp^mE;*@>rO4 z(-OL=iP^oqw{?4hprFIPuC16Ak*+3o@w;SamGL zn|c^gvh?)dlb*T;`L;I^gbUl``Gy~#&)zU4_r(pIJqu-^NL(7#W^(2O`Pfvi^>%Rb zr1PNQ=eK`8P>%Abnl{@PphAZV7bGm?)`Ng|a!Roryzy$z$U#*soZ-k=tU51S847I6 z_eDX8!8EwA?;x?nsOVL zcxCF)lFc}a-nt*TTAJJEcJ6fyg1%9KTN+;YZ@z2%xa6!vw&R(nFV^_xC^~Xu6LV^b zyx3rqTM?4ksa2$7f>QaVvZb>8#>g)6$w@r#^QdFELn1R31qr%h@~?t}s=t$qRxGGa znLz=?{mK?DY}L_tDDuK$Ba7lJECZCOgv^>d zH~#u`*FE=1V0bpuznK~v_asf0hEp(M6?}^?u*oMM7mj7j#&Y~sybw)uY1uSN+J~(_ zhLFHZolv#a~B!fgV%WsY~2nX3`)CM_x8bJ7nEuJ+ovqo2fiA z;Gn~_MELDek$J4w4%107%`pk{bAuMYQk=Rvby)b+JuI;Iq@soxdVQ>nn<$t1X5~55 zP0!D1JR*3>6Rp-wx!)2tc@5c3JPr0@XbXoroaMGFClTwiOM%0yU?zihnAM~kxb*}& zW$bPw892upXJq0wZQw4I6S#fKM|*(MaNB;eJ70)p)6jt$M&9lyCHGHz!ON#;kv{l) z%tG*xO4d*meu}Ffz#%c8b&HmB+h)xc&xs@rAHWXgQ;N$?$WWw;Sq}goYy`JLar=~` z2h)mZ<=ze+>iPPfh^LRtn1RN^1;wZ(f^R}eSwWY@SIkY+21=B5Z zo&%phVWhuHW31)8Doa_wND1ojN>~ixXz7B)cIQb~%hJn;o21tZ3?-Xj!B@r9&zU7u z@hD{J543d>hnkczFXfz@dKb)Dda&_Zzx^l$p!guLgtXjSL=Cr@%jX3Y0tZd;#;}@~ zL>_E`o)#TV!Q87Pibe@Az-N9e{Y{{I%m17p6JCk;>f%hRuo32fvmnnx{$<+^;`{?1^x|F=~4CWcUVlPaCugbh*aFTtzl#MRKz>B0TPJ7+D)!I`lNdEO37 zrJca=+|etz?eI}avMNiYHxAENInWkC>uMqluTvfWz~jgf3OJf(RVB=)^faf-sDMfFcfd1JXu1qm#7z9;jRoh zH)#iD44ZEUu{fc59WFiD96U?EHDelo(ZXk=32zLu%rZN}`HH=NacTtmx~KbmCEj)7 z`PmoI^tM7205C;kGW=Nk{CBK^2Atn|HRa^8N|n zxX7)eBOpmrGVntKdUt?Ps^=Yxp& z!4RTCvBS4%kls;0AK+)80e~Ko$HGv-pXR$)X@pKc-L&%b007ocZ%1;=K?I$e|IG`! zp<$!>1jdK&M|TEkCnY^?EUTJ0#WOqv-Az=US81W$6`%=zi57+zw4!R_2TuA60*xJ==;?4V@y_^>Z3oTe*$3IEQ7Fz2 z9IwqEa92+5xBADpOeXREL1k0sd2c+zveE3*S86S@_qz@SmLGsih+lCQ;vxW9L;lT(piw+B=b&p`u3&hM zuZO}mdRU_^bhbo(+rO;vYj^Y;Id6Ih;4kH$mozwex!A=BV3ZK-niu$HL0>KGm9&Eo zJ|wumajg04^?rE~vsf>z{x;pL*F@;w);Ey>+3*d<^l9-%Z}W_bu0rrO73LSh_#a6= ze7lyG9;;A6pBxy4vsYVQ2Q#}@W8|g)=GKe1n|&+FhfRT|!yUmwwVN=efN16xWCqXU z$+Lm#-I{^(x+Za6m2czpr7aH4gDJf2OK`Y%KaRd2lrWijTBvn*>KXLt`)+NZsmq(V zp_`_9UvDMISKF}VD0I5KTHHZ<`}Gz$mAJkKQw5A(yvtSTPyGC|vchSb<;kV)p1Xg` zo71zU@K^fT_a=0f$2_#Cr4gYfu3G3KY}w}*tRBI;t~lDjTzWW{!NMkTVr2w}m6?+ymtn#pzvH$qJvaQmt@3SL z+5KhWsz|I-d`%`Sas zZhf6&PLF+ZysNMRB5+frWuusV*H{cNkkRQfDcxY`^m>;pUg`I26$~s>H+>bz52lgi z4cy%h?02Ax!kM#8e|^T9!VmkB>o_d9vZNW9`$)cM_4s5ipBos--zKsJiuj%55qgs9 z6ZZyg>g@Dqx)UEbd%eG6i4vEbQqKw+9j=lBi-@jWLrqJSJ2L}eHr86?s=)~7( zde2g0%Ahu;RF=|aH-pz0a~L|B_+hpRIby5Nz+N;hCHHUC#?4CkMHis9Xmo|rS}xK$CvRGYn3h+n1AQ3XN8l2HaoCaGtyLef||uks#!p3 zDj}94bGj-k>nyMH-Mz9bGLu5C4I?WHN$&0CT2C9#YHxIqC&wg5@&clhjf&&RH&osc zY!sX8H>$@Xb_DiW%k5l}V>RRja*HZ0E}4!S^ie#PQ%&3V;~OeiRyHCIuIR5;WG$h3 zukC4YIbRdX4FycCY5b;{SjHvk{5m26O44CXTJLWJvtGbRtAGP&Ry(9d?CWZ#K=EOT zgS}H!ho;(%-XSc%b{4JjHRs8$Teg{b^5oI9=XRao0b`(q!A&D~h4<$`Rd@tmr$~mv zNrMHPcJpX!FBs1eUss+UQ13zr?HSeZmlupby_P$iD7=>;slTe!6%Lv!0|otaLf*%L zXm95iQO!q|&Yct2YS@!31;1R~n*kCs*}+S-^hAFJ-n&Wb#Mxfh*gc>cutCZp2{{5$ z^0}eSVF4rQ-XZ?npoxsYp^jgM9pqN`pN~6MZgk&>{LG|mPUIXFs`Jt;v4m{`HUi)n^mG}^$4d<_68_Jbf`!E?pAH?9>td6 zpI|#h-y0fhqxkYkTg0vZ5Qr~=bMEd*8C5BA4fLm&KwW{Sdb!-% zWlh&-Slv&jLuZ}$g?>HG8MsH%JW$voq3-Rqjm7eE(L%S*26OYdIlSb1=WqPJ(i5oB z*M9G`?5XSdxR5=`TVM>qU@eYrmQ8#O=b~%}Wlb>1?vV_pM^f^nnM~T*)iKmD!S78(JMg>! zo3Fxe%amHW{qn_uk~LCEkV}bUukKd)Bxx^5kbqRdg4+F}!Z{u?{<&n$#_M7f6rR!J zUM*#}VW-P=*%L`$ZY6Qe7K{Mt6rBD>qlo0RnDK~pTZ7~W<^)Z=k0V=EB_P}JF8F^U z?Au)=&X9RncBgqajp~oG%FCiNB{HeEy)fh24bwJ3akU8+ znHX}M<`UhQLIQHkk5*SftVYTbIyxdW1fqD#qDq5%)q3Vtxw&2~h)|R0kiW8Kd=&^W08;|Y-&fER3AYo~P_Njl} zce9($8@A2x70?tMnx=2Qs@M9}N#wlyX zt7*Fr760p^SuTDTydz`}!#}U}S0QbEsk_VY4_J^P-d1cSzviNGiucl}{3Zq2CQ>kB zeCWY;q3a=*=;D-d{SM)E?r`}jt$Mt8QYPzev9{0F_c{0?5l?cFsT1P%qI+3?|DrM! z5pKN3*R9)L1tifD<$L9EdP3CQqg$bLJnseSh%Z3P)v@}<4}OT z!FM~@^u|ZAq@1249(#}aCc0-&mPK)9#rVd}5Hkk9sruVe#X(_Xa4X3_yy6`HecM0c zCA#A}xym5+*#v>ly7aRg=Gnrxe@g7o9z`#{6&>nk z2lsR^aLg$BZVxBrt6yE+jASwK5;@>SJ*^prI1FTXm_(cqSoJMhTDBE%c^zL_7~aS7S5_dg zq}jX;4e@8n++~|nc4W4O31utO4r|?ip}`F@U$1peDrfjwbOP~u_SiD4B?VN<+PZV= zq0$T%LQGtD8cFNwLaP@nUuraM#0YOwCfCqOAs6?0}*-40nt%UN_s;t*dd#Ac^52Cqa@DbM}g z-_jBUgtu`D%#xHMT@JG(x{B~7&57p6>R+B8UScUy=J3_T6zfV$b%QmkHnA;a3H^@X zwzwQgRau=&RM)_Yv5Za2mf4+&Civ}a>$P{!vnw?U>h|~{r&f;POIT{I*qR*$<(U_~ zpmJ&HF!D-Y(L<{BoiEh7^QiG?=hg*rJdu=s)$3S)NbEXfJgF+LJ`| zyiwFS3Ot^_yly-i2# zdK)II7A9D7?mMuZDlx_{Gu)|lr&&py%%Og{W;pyxE+m6Zd^z~AObwJ|Y-De;l{rd~2G9TPK2Mv`c31*eN~qRyVPj$E3X z4+L03noW{hpV|3J6(s4+sKVzhiaP%6mCDjY^za1&!Zw^itC)?({__a$ zsmBfaram-J;i*+ZIA*GfAs-AD46v{{VlJK`1xYzELX^q3N>HACs&I0Q!r3$JcY*5C z@LG@ok2<8hp zvhrONDe`uc_wz~Zo`qK*>C+eYG*5V-q^Y%&G}_DtT3k+1a2dRo$G zIe4u}RAuQm{bD&3mKy{5`Q6QB)BRXF^Is?ld|eyWBAsjVH z^^%=FV0~hBtYM#@j&ByC0zfmHmxlFF{ot{H_du2OPk$V)vB(u;a*+!pT~7l$&JmsC z8=DG>;F_0PEjGge*&cAptacy4F^Emuns2_jYVCT)O-?QUtW zpP!)R<5^jrjv=4m@#E_XWJAk>>N`l_yZsg2W>srqM|c$-UDPdWyhDVExqW(Ex{;Ia zPF$wB=q3gUwj?&|E2xdrC&D6CeESVQ8a=R08veY(v`VDS6~8Rj?HjkyID^jz*$>&7 zb%uu>b>!2YCW5BgmyO9Q&ubfURq$<|bW+|nWj`o&t z!xaKN++{;*ZwWT7-CuE}C)<*Gz= zS;y%250F(01TquD14ul{)03gfy{~&sqBYSxB zfDG$(8{=fo1jPgxlV+f*w~hG7$TiNl$T7&*wlpm6rB%)@50 zw3V-_;3R|U5_DOA9u~(I#G4E4-zmgSnCs?{6rXkkrmHEb^#wHH*#H@cxP5tP=_`tw zTlAh7fvvPTz}*$6iOB}!NK2y9MWr&A z!604M?h<(eOECXhtEU+RYJh$h2DBpi9#da@#P z_;Q{1spPEdX%C`@s$9$Bt?|ydMCf*{22ve=m$7GL4?Z0fl?tz@)VNnOF#Q6h0 z$E=yI-kYv#iSlRLm1==ux*RwGjYj?P26Vk9J!)!mT`2$Ki--*5Dc;+*pG0g>SyiZk z96bRKbO^SoWmq^gf*dwBP@x4VXnB zJsq5hU9yQd^O*G5EfCXY)!{|-?=n_~wRUmo@-sSf!@>8u35x4jrsr+NA{Ta~IakG8 z%*4Khd5o53%UT2`pOlzdL^~Xn5o;P$bn=-)9E*EIP0{n`$ryb?xJ>k(+K4jLqTdcg z7w_RT#~TYfwxkUxHVRMy1u?GL4sZ0rWX(M6UEW2f-2CZfJ#S~j?`=uTQD((EN)&qY z1KNa55VH3GG@=9g%h@+b(6)i=aALh46!K4M&sxwG+jSExX&HW*U9gsBB+& zTr{B%LU0S7k=R9)+jH*qC1{&u(kF^P8ZpZ6@JJ8||8ebYeDQB@@Hv%zdm06Xve+M; z9UZUt@5#5np(hdvrCtg$vrftBsm zMJvJL2eV70*54?MtUMBKy}T93)v&x$I6^_zzhQ$(Ksc5J^tStbHs<_<7z1%pLo@z{ z5qzrn-}ppQc7nxaq1=;f@xmafj0n)Z2t5Gf_lClXNGjx@3=2ovuE4QEdrzo8IQ^#9Aa>^7}0ZA~(ej(p>WL zo-qer=IY{Kl6dQb=O6M4a^%|(rqWua_X_k2<&3VvOI0ZxK?#>UGC_Tl%P#Ss*7a891scGfdu)%3tu8*Cd;mJ{5V|rRL zyF%*Vz=ZpOX9zTDVvPbMD|o{nHC+xB%I-LMMpbid+zDA1`|s&t-d3{5Id;O9O2g&- zL9>Y-y+xrz*QcnAi{ca%Xrb_lVndz|3e}5G|ZySG~_^xSQ(l zGnPdCu#Z;m*CVaxRgF}0-AgCCR`p@uY`$}{O~%n%gP)9sUZL&G(u50OTphmlu6HGQuWK7e1Qx9CG0>mzH3J%N9Ai;g z2XBpsyv{PG=?5gtVm!|*D5nmi4^wtVKSO`amEZJh0f1gM$p4y1YGC5c)F110tb-Yz zG%)are0^)MsH31L7+hE97^du9WV8?Z4@@U#L?OQft-bD{0mxYs3#Ws9WKpuAsP# zkSadqglFC2HJ8l)Tpm*tewJbr#v*HIeAwXfy*Qh zgZ~un{=;cy&PKyxm&`d&Fv0V4=Ik@M<}0zNdVgkN`0?5gf(S_`=HGJP$k>eevrO=o z8Q`z=^yz5mEPmlywtE~+o#8Eu1_eEFrMsqLCG;7;2Sh(ruGEq7tuInj6OB(k z#IlulId2PD*|`DJqp%Jtb=h^{ty}G*^$r)Ej*(x>rv66r6bYss!aLT;5(Rn%0SFZQR$^lOwV8L-}Mn%8MZ zYNXVGiRSiS@J@C)siikZr-2kTFQs`Ets{)hVx^1ElbRH;N9q zgi$&xlnHwp#bzdFaMX=8ApTE4A+a3VJK+tB0*m|dGwaO(;0`R2m87_rs$#ShQ&H6# z5aIo#e9JXaDA!!NfoSodTvJkv6`QkzH~9x}5-$sIuoE2ONZ@rgnmDZG33{xRBsm^N zh}!ej#>Ui6kz~Tya783&PN}S7VcoUue=2M_W#i^kZj^vXyz5~q8}X-1*JO3O{4z+M zoYqy+Rn%@RyDKZ8jz5e7;kSQjh4)Ms5FZ)zg_EME^Yd9c^q4e_g*@%Z$YfTbI`?U# zN@^iQ7|Ckn7|@xI1L4Jr$zptR2Tji218>|2>YA-?bE~q7DLRW|fJmrc%0Gq&QQ7Eg zunDtj@}lWtjLvkZ=GT(6XC9~8Hv{D6*3n}kecY={=c}$Fx6sjNy&o9dOeJY@c9?_( zh`Wz%rKuE~_`68o=Vr?H3G3JfIKRR^x+yZ^eslnMY22e~|9C|RfpZGTU&~Xv7G%YQ z!(=?OWiJxgmbB$3nHMeQ%w>yPigAzglZpZ)gm*pP%IIz9T`ytDS&UYav)#{WJX%|u z3pCd#n3C+D_2*RW@7E`_STM;RkIf6h>khoUVBFWNRLUNw%BzaUS2)S&`j`LbZuj_d zZ35FbS4@9k4$!|3Q9{dJB2}jTiTn*qMGR>IF6Yy+%Ew%^3AvRj9b~;j>2t6? z{8GlToyt0|AAaU@J4my7!}?hgsA+|iW_y2-(7r=Ck2}LH{+y74bFcLYkO&2)1N5RH zKYj>Q^fMqn)ZTzk6CcXyFHA5Wb8IjEhfo~w-)$T*nDBepWw(RMrw6#hK{)+nioDXZ z31v4cl>11h4<7vN^Slqt{zsS~fQ=CqU_T+i*ZOcvNN#vZH@@Y2e}V{3673Z!@NNpNZvZjFgsVVuJiET5yk4w;&L=&i5- z_utO`!w3FW)yn>38o-2y=>qgb5gU|JQ>?`bcR2s&;mntr8Ain6)Os|1X)ocv5|P*-R7wJYS`vX4GechNOX zXI1hI(M;nZ!+UFOudBz+HOeinyPh0fghm)6`>of*@n!k*E+5P3K|k@Tln!&E$ln+5 zU5D4X{G~xdas&WVSk_Hm{l*Qhw`u3+9l2u8cjqhln#B_r3Q_x^?3Q;`wet4Ushdap zK(Xbs@!WMvAL1Mt@BJ6kHqf48nmc$Okx}jVZmY}BeQOr3syF$)!RB$hmt_^Sr(7BbbED9olnKDS#unRcp85I5`&R#|=JtF{?I|zv#vSkJJVccANb+ z%x)*u+f&;G?v@|{8iH{+VF>o62vW;=k0zr_#%RXZm~f-5PlhE9e{q7cf{$~(7sEa! zp;(_*_9=kt$*L+z>9-FHxqXlr?#;2pN;`TCUb?K12T8lVz;ALpNvGk-Y*s)SmYIKR zC=@%IOUjCJ{}6ZBsVtEgwSEsS%@W1@O7k^xw5A{=mG)4P3rP@(2qmkA$nny96bA~T7i|E|0;wqByuW^|C%d8-Gw3ZP zLqetRPaJw-*(l6kx)2^MQiE#8>mOE~eqwiYgG@^4QDrr2sv054m^^TqM3yHoyjLiF z@kIj^_t%H~vq|}53lREh%)m(qk?}s=twG(bG$GK@$gequ59-8^rk7STg1}BKd8eywy>wG`+ZDo4a+gl2)_+l3!> zTIbiTo+o9bg9q7LmQLk75*I+4$pkBh+I8@ju|8Ad%d_X~P-5WkdwR;02k*MOehc)E ze<{c^GPF>okXPLiaz@$$E&Bho0p!iZkv?R6dG6(2FaRdbpPL^;-8{m7`5%h|KG}z+ zi_SVDSS*`QKb1as?{@P$<{#?IandleL&o_pHlByc7}Z+)>HG7Si+4;py!ZSq0$g0E zdJ}`3@R>hq@pY!tT3V{LQ3Htn6zgOwE(P}!LByINDl9^vnMEi*{U_y&?v!VBN@Q6@ zSuRhDn)emsvOv#cS>oB;MGk#hj)=b{p!DXCzKnzA)Z?tI(Vf6xX;~Q}l6>I#CfyN% zhE|%hTi7lAfkI8c{dW4w%4=O6B|JKh<)4t3HRocYLF5%8FR(Z(Ty|pP7XS08nO-)7 zSY{7haJX@D&UAXM7sAKl`liQxPyAw|==ED**9hD0?z{)p57_qm@fLcg1M@ki-+8s* z6SqTn#lnKqdW#!WGaq*A+RR+v?U%g}gC^z9wzWa|7pik&JFE5N8f$jhm)ns?`IXCq z@tzmmz~r6=LXLRR<6;QBW|%AIg-h9p)A^lId%gs7ylXP<0vJJ`%-&)34_4k<*QTT< zW_T{i_gd7E9wXA@DAMZHL$j2wJ?b2=~!M?5w{3%<5@>khH~J~|;sJ@J|8y>nO`g#P?Q8LLN6 z>SDCVE|PfPg!PV(6-s2={kV}VLfO;BfaGH^9gt!K!;su&_ZX-=IP8Ala<*KhcVJr} zHEaw?u#Y95HT}y_-4R43+M@`JE_wOFqt@9hqc{A4dF7us(9L0ja1 VbHS6LeQ@(S6;(|kMx6Q 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℘JHla$t|!U@ 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ⅆ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 + {{ range .Repositories }} +
  • + {{ .Slug }} + {{ if .Description}} +

    {{ .Description }}

    + {{ end }} +
      + {{ if .Language }} +
    • {{ .Language }}
    • + {{ end }} +
    • {{ .Stars | formatNumber }} stars
    • +
    +
  • + {{ else }} +

    No repositories found.

    + {{ end }} + +{{ end }} diff --git a/internal/glance/widget-trending-repositories.go b/internal/glance/widget-trending-repositories.go new file mode 100644 index 0000000..77b1373 --- /dev/null +++ b/internal/glance/widget-trending-repositories.go @@ -0,0 +1,113 @@ +package glance + +import ( + "context" + "fmt" + "html/template" + "slices" + "strconv" + "strings" + "time" + + "golang.org/x/net/html" +) + +var trendingRepositoriesWidgetTemplate = mustParseTemplate("trending-repositories.html", "widget-base.html") + +type trendingRepositoriesWidget struct { + widgetBase `yaml:",inline"` + Repositories []trendingRepository `yaml:"-"` + + Language string `yaml:"language"` + DateRange string `yaml:"date-range"` + CollapseAfter int `yaml:"collapse-after"` +} + +func (widget *trendingRepositoriesWidget) initialize() error { + widget.withTitle("Trending Repositories").withCacheDuration(8 * time.Hour) + + if widget.CollapseAfter == 0 || widget.CollapseAfter < -1 { + widget.CollapseAfter = 4 + } + + if !slices.Contains([]string{"daily", "weekly", "monthly"}, widget.DateRange) { + widget.DateRange = "daily" + } + + return nil +} + +func (widget *trendingRepositoriesWidget) update(ctx context.Context) { + repositories, err := widget.fetchTrendingRepositories() + + if !widget.canContinueUpdateAfterHandlingErr(err) { + return + } + + widget.Repositories = repositories +} + +func (widget *trendingRepositoriesWidget) Render() template.HTML { + return widget.renderTemplate(widget, trendingRepositoriesWidgetTemplate) +} + +type trendingRepository struct { + Slug string + Description string + Language string + Stars int +} + +func (widget *trendingRepositoriesWidget) fetchTrendingRepositories() ([]trendingRepository, error) { + url := fmt.Sprintf("https://github.com/trending/%s?since=%s", widget.Language, widget.DateRange) + + response, err := defaultHTTPClient.Get(url) + if err != nil { + return nil, fmt.Errorf("failed to fetch trending repositories: %w", err) + } + defer response.Body.Close() + + if response.StatusCode != 200 { + return nil, fmt.Errorf("unexpected status code %d for %s", response.StatusCode, url) + } + + parsedDoc, err := html.Parse(response.Body) + if err != nil { + return nil, fmt.Errorf("failed to parse HTML response: %w", err) + } + + doc := (*searchableNode)(parsedDoc) + repositories := make([]trendingRepository, 0, 15) + + repoElems := doc. + findFirst("main"). + findFirst("div", "class", "Box"). + findAll("article", "class", "Box-row") + + for _, repoElem := range repoElems { + nameElem := repoElem.findFirstChild("h2").findFirst("a", "class", "Link") + name := strings.ReplaceAll(nameElem.text(), " ", "") + + description := repoElem.findFirstChild("p").text() + metaElem := repoElem.findFirstChild("div", "class", "f6 color-fg-muted mt-2") + + language := metaElem.findFirst("span", "itemprop", "programmingLanguage").text() + starsIndex := 2 + if language == "" { + starsIndex = 1 + } + + starsText := metaElem.nthChild(starsIndex).text() + starsText = strings.ReplaceAll(starsText, ",", "") + stars, _ := strconv.Atoi(starsText) + + repositories = append(repositories, trendingRepository{ + Slug: name, + Description: description, + Language: language, + Stars: stars, + }) + } + + return repositories, nil +} diff --git a/internal/glance/widget.go b/internal/glance/widget.go index 50dc3cb..4361c44 100644 --- a/internal/glance/widget.go +++ b/internal/glance/widget.go @@ -81,6 +81,8 @@ func newWidget(widgetType string) (widget, error) { w = &serverStatsWidget{} case "to-do": w = &todoWidget{} + case "trending-repositories": + w = &trendingRepositoriesWidget{} default: return nil, fmt.Errorf("unknown widget type: %s", widgetType) }