import * as YAML from 'yaml' ; import type { CountableSlugify } from '@sindresorhus/slugify' ; import type { Await } from '~/types' ; import { typedBoolean } from '~/utils/misc' ; import { markdownToHtml , stripHtml } from '~/utils/markdown.server' ; import { downloadFile } from '~/utils/github.server' ; import { cachified } from '~/utils/cache.server' ; import { redisCache } from '~/utils/redis.server' ; type RawTalk = { title ? : string ; tag ? : string ; tags ? : Array < string > ; slug : string ; resources ? : Array < string > ; description ? : string ; deliveries ? : Array < { event ? : string ; date ? : string ; recording ? : string ; } > ; } ; type Talk = Await < ReturnType < typeof getTalk > > ; let _slugify ; async function getSlugify ( ) { if ( ! _slugify ) { const { slugifyWithCounter } = await import ( '@sindresorhus/slugify' ) ; _slugify = slugifyWithCounter ( ) ; } return _slugify ; } async function getTalk ( rawTalk , allTags ) { const slugify = await getSlugify ( ) ; const descriptionHTML = rawTalk . description ? await markdownToHtml ( rawTalk . description ) : '' ; return { title : rawTalk . title ?? 'TBA' , tag : allTags . find ( tag => rawTalk . tags ?. includes ( tag ) ) ?? rawTalk . tags ?. [ 0 ] , tags : rawTalk . tags ?? [ ] , slug : slugify ( rawTalk . title ?? 'TBA' ) , resourceHTMLs : rawTalk . resources ? await Promise . all ( rawTalk . resources . map ( r => markdownToHtml ( r ) ) ) : [ ] , descriptionHTML , description : descriptionHTML ? await stripHtml ( descriptionHTML ) : '' , deliveries : rawTalk . deliveries ? await Promise . all ( rawTalk . deliveries . map ( async ( d ) => { return { eventHTML : d . event ? await markdownToHtml ( d . event ) : undefined , date : d . date , recording : d . recording , } ; } ) ) : [ ] , } ; } function sortByPresentationDate ( a , b ) { const mostRecentA = mostRecent ( a . deliveries . map ( ( { date } ) => date ) . filter ( typedBoolean ) ) ; const mostRecentB = mostRecent ( b . deliveries . map ( ( { date } ) => date ) . filter ( typedBoolean ) ) ; return moreRecent ( mostRecentA , mostRecentB ) ? - 1 : 1 ; } function mostRecent ( dates = [ ] ) { return dates . reduce ( ( recent , compare ) => { if ( ! recent ) return compare ; return moreRecent ( compare , recent ) ? compare : recent ; } ) ; } function moreRecent ( a , b ) { if ( typeof a === 'string' ) a = new Date ( a ) ; if ( typeof b === 'string' ) b = new Date ( b ) ; return a > b ; } function getTags ( talks ) { const tagCounts = { } ; for ( const talk of talks ) { if ( ! talk . tags ) continue ; for ( const tag of talk . tags ) { tagCounts [ tag ] = ( tagCounts [ tag ] ?? 0 ) + 1 ; } } const tags = Object . entries ( tagCounts ) . filter ( ( [ _tag , counts ] ) => counts > 1 ) . sort ( ( l , r ) => r [ 1 ] - l [ 1 ] ) . map ( ( [ tag ] ) => tag ) ; return tags ; } async function getTalksAndTags ( { request , forceFresh , } ) { const slugify = await getSlugify ( ) ; slugify . reset ( ) ; const talks = await cachified ( { cache : redisCache , key : 'content:data:talks.yml' , maxAge : 1000 * 60 * 60 * 24 * 14 , request , forceFresh , getFreshValue : async ( ) => { const talksString = await downloadFile ( 'content/data/talks.yml' ) ; const rawTalks = YAML . parse ( talksString ) as Array < RawTalk > ; if ( ! Array . isArray ( rawTalks ) ) { console . error ( 'Talks is not an array' , rawTalks ) ; throw new Error ( 'Talks is not an array.' ) ; } const allTags = getTags ( rawTalks ) ; const allTalks = await Promise . all ( rawTalks . map ( rawTalk => getTalk ( rawTalk , allTags ) ) ) ; allTalks . sort ( sortByPresentationDate ) ; return { talks : allTalks , tags : allTags } ; } , checkValue : ( value ) => Boolean ( value ) && typeof value === 'object' && Array . isArray ( ( value as { talks : [ ] ; } ) . talks ) && Array . isArray ( ( value as { tags : [ ] ; } ) . tags ) , } ) ; return talks ; } export { getTalksAndTags } ;