@@ -222,8 +222,94 @@ const buildSpeakerPerformers = (conference: ConferenceProfile) =>
222222 ...( speaker . linkedin ? { sameAs : [ speaker . linkedin ] } : { } ) ,
223223 } ) ) ?? [ ] ;
224224
225+ const buildEventLocation = (
226+ conference : ConferenceProfile ,
227+ locality : string ,
228+ region ?: string ,
229+ venue ?: string
230+ ) => ( {
231+ '@type' : 'Place' ,
232+ name : venue ?? conference . locationName ,
233+ ...( venue
234+ ? {
235+ containedInPlace : {
236+ '@type' : 'City' ,
237+ name : locality ,
238+ ...( region
239+ ? {
240+ containedInPlace : {
241+ '@type' : 'AdministrativeArea' ,
242+ name : region ,
243+ } ,
244+ }
245+ : { } ) ,
246+ } ,
247+ }
248+ : { } ) ,
249+ address : {
250+ '@type' : 'PostalAddress' ,
251+ addressLocality : locality ,
252+ ...( region ? { addressRegion : region } : { } ) ,
253+ addressCountry : 'IR' ,
254+ } ,
255+ } ) ;
256+
257+ const buildEventOffers = (
258+ url : string ,
259+ startDate : string ,
260+ endDate : string | undefined ,
261+ isPastEvent : boolean
262+ ) => ( {
263+ '@type' : 'Offer' ,
264+ url,
265+ price : '0' ,
266+ priceCurrency : 'IRR' ,
267+ availability : isPastEvent
268+ ? 'https://schema.org/SoldOut'
269+ : 'https://schema.org/InStock' ,
270+ validFrom : startDate ,
271+ ...( endDate ? { validThrough : endDate } : { } ) ,
272+ } ) ;
273+
274+ const buildScheduleEventDescription = (
275+ event : ScheduleEvent ,
276+ conferenceLabel : string
277+ ) => {
278+ if ( event . description ) {
279+ return plainifySync ( event . description ) ;
280+ }
281+
282+ const title = event . subtitle
283+ ? `${ event . title } — ${ event . subtitle } `
284+ : event . title ;
285+ const parts = [ `${ title } ، بخشی از ${ conferenceLabel } ` ] ;
286+
287+ if ( event . speaker ) {
288+ parts . push ( `با ${ event . speaker } ` ) ;
289+ }
290+
291+ return plainifySync ( parts . join ( ' — ' ) ) ;
292+ } ;
293+
225294const buildScheduleSubEvents = (
226- conference : ConferenceProfile
295+ conference : ConferenceProfile ,
296+ {
297+ eventUrl,
298+ conferenceLabel,
299+ images,
300+ isPastEvent,
301+ locality,
302+ region,
303+ venue,
304+ } : {
305+ eventUrl : string ;
306+ conferenceLabel : string ;
307+ images : string [ ] ;
308+ isPastEvent : boolean ;
309+ locality : string ;
310+ region ?: string ;
311+ venue ?: string ;
312+ }
227313) : Record < string , unknown > [ ] => {
228314 if ( ! conference . schedule ?. length ) return [ ] ;
229315
@@ -234,35 +320,53 @@ const buildScheduleSubEvents = (
234320 . map ( ( event : ScheduleEvent , index ) => {
235321 const eventDate = event . date ?? conference . startDate ;
236322 const startDate = scheduleTimeToIso ( eventDate , event . time ) ;
323+ const endDate = event . endTime
324+ ? scheduleTimeToIso ( eventDate , event . endTime )
325+ : undefined ;
326+ const sessionUrl = `${ eventUrl } #session-${ index + 1 } ` ;
327+ const sessionStartDate = startDate ?? conference . startDate ;
237328
238329 return {
239330 '@type' : 'Event' ,
240- '@id' : `${ SITE_URL } ${ conferencePath ( conference . slug ) } #session-${ index + 1 } ` ,
331+ '@id' : sessionUrl ,
332+ url : sessionUrl ,
241333 name : event . subtitle
242334 ? `${ event . title } — ${ event . subtitle } `
243335 : event . title ,
244- ...( event . description
245- ? { description : plainifySync ( event . description ) }
246- : { } ) ,
336+ description : buildScheduleEventDescription ( event , conferenceLabel ) ,
247337 ...( startDate ? { startDate } : { } ) ,
338+ ...( endDate ? { endDate } : { } ) ,
248339 eventAttendanceMode : 'https://schema.org/OfflineEventAttendanceMode' ,
249340 eventStatus : 'https://schema.org/EventScheduled' ,
250- location : {
251- '@type' : 'Place' ,
252- name :
253- parseConferenceLocation ( conference . locationName ) . venue ??
254- conference . locationName ,
341+ isAccessibleForFree : true ,
342+ organizer : {
343+ '@id' : organizationId ,
255344 } ,
345+ image : images ,
346+ location : buildEventLocation ( conference , locality , region , venue ) ,
347+ offers : buildEventOffers (
348+ sessionUrl ,
349+ sessionStartDate ,
350+ endDate ,
351+ isPastEvent
352+ ) ,
256353 ...( event . speaker
257354 ? {
258355 performer : {
259356 '@type' : 'Person' ,
260357 name : event . speaker ,
261358 } ,
262359 }
263- : { } ) ,
360+ : event . type === 'panel' && conference . speakers ?. list . length
361+ ? {
362+ performer : conference . speakers . list . map ( ( speaker ) => ( {
363+ '@type' : 'Person' ,
364+ name : speaker . name ,
365+ } ) ) ,
366+ }
367+ : { } ) ,
264368 superEvent : {
265- '@id' : `${ SITE_URL } ${ conferencePath ( conference . slug ) } #event` ,
369+ '@id' : `${ eventUrl } #event` ,
266370 } ,
267371 } ;
268372 } ) ;
@@ -278,10 +382,18 @@ export const buildConferenceJsonLd = (conference: ConferenceProfile) => {
278382 const attendeeCount = getAttendeeCount ( conference . stats ) ;
279383 const keywords = buildConferenceKeywords ( conference ) ;
280384 const performers = buildSpeakerPerformers ( conference ) ;
281- const subEvents = buildScheduleSubEvents ( conference ) ;
282385 const pageName = plainifySync (
283386 formatConferenceLabel ( conference . title , conference . year )
284387 ) ;
388+ const subEvents = buildScheduleSubEvents ( conference , {
389+ eventUrl,
390+ conferenceLabel : pageName ,
391+ images,
392+ isPastEvent,
393+ locality,
394+ region,
395+ venue,
396+ } ) ;
285397
286398 const graph : Record < string , unknown > [ ] = [
287399 {
@@ -347,45 +459,15 @@ export const buildConferenceJsonLd = (conference: ConferenceProfile) => {
347459 '@id' : organizationId ,
348460 } ,
349461 image : images ,
350- location : {
351- '@type' : 'Place' ,
352- name : venue ?? conference . locationName ,
353- ...( venue
354- ? {
355- containedInPlace : {
356- '@type' : 'City' ,
357- name : locality ,
358- ...( region
359- ? {
360- containedInPlace : {
361- '@type' : 'AdministrativeArea' ,
362- name : region ,
363- } ,
364- }
365- : { } ) ,
366- } ,
367- }
368- : { } ) ,
369- address : {
370- '@type' : 'PostalAddress' ,
371- addressLocality : locality ,
372- ...( region ? { addressRegion : region } : { } ) ,
373- addressCountry : 'IR' ,
374- } ,
375- } ,
462+ location : buildEventLocation ( conference , locality , region , venue ) ,
376463 ...( performers . length ? { performer : performers } : { } ) ,
377464 ...( attendeeCount ? { maximumAttendeeCapacity : attendeeCount } : { } ) ,
378- offers : {
379- '@type' : 'Offer' ,
380- url : eventUrl ,
381- price : '0' ,
382- priceCurrency : 'IRR' ,
383- availability : isPastEvent
384- ? 'https://schema.org/SoldOut'
385- : 'https://schema.org/InStock' ,
386- validFrom : conference . startDate ,
387- ...( conference . endDate ? { validThrough : conference . endDate } : { } ) ,
388- } ,
465+ offers : buildEventOffers (
466+ eventUrl ,
467+ conference . startDate ,
468+ conference . endDate ,
469+ isPastEvent
470+ ) ,
389471 ...( subEvents . length ? { subEvent : subEvents } : { } ) ,
390472 } ,
391473 ] ;
0 commit comments