event details

This commit is contained in:
tree🌴 2024-02-24 02:12:40 +01:00
parent fb7b4d070c
commit 9281234215
7 changed files with 227 additions and 121 deletions

View file

@ -1,104 +1,13 @@
---
const { item } = Astro.props;
import { format, compareAsc, addDays, isFuture } from 'date-fns';
import { marked } from 'marked';
import EventsExt from '../events-ext.json';
import core from "../core.json";
import { imageMetadata } from 'astro/assets/utils';
function findExt () {
let slug = null
if (item.links?.web && item.links.web.match(/^https:\/\/lu.ma\//)) {
slug = item.links.web.match(/^https:\/\/lu.ma\/(.+)$/)[1]
}
if (item.links?.rsvp && item.links.rsvp.match(/^https:\/\/lu.ma\//)) {
slug = item.links.rsvp.match(/^https:\/\/lu.ma\/(.+)$/)[1]
}
if (!slug) {
return null
}
return EventsExt.find(ex => ex.url === slug)
}
function getSpeaker (id) {
return core.people.find(p => p.id === id)
}
const ext = findExt()
const isDate = item.date.match(/^\d{4}-\d{2}-\d{2}$/)
const future = isDate && !isFuture(new Date(item.date));
const dateMatch = item.date.match(/^(\d{4})/)
const year = dateMatch ? dateMatch[1] : null
function dateFormat (str) {
if (str.match(/^\d{4}-\d{2}-\d{2}$/)) {
return format(new Date(str), 'MMM d, yyyy')
}
const qm = str.match(/^(\d{4})\/(\w+)$/)
if (qm) {
return `${qm[2]}, ${qm[1]}`
}
return str
}
function nameRenderer (item) {
let num;
let ccm = item.coincidence?.match(/\[(\w+)\]/)
let cc = ccm && ccm[1] ? ccm[1] : (item.coincidence ? item.coincidence : null)
switch (item.type) {
case 'summit':
num = item.id.match(/^w3ps(\d+)$/)[1]
return `W3PN Summmit #${num} ${item.city}`// + (cc ? ` @ ${cc}` : '')
break;
case 'meetup':
num = item.id.match(/(\d+)$/)
return `W3PN Meetup ${item.city} #${num ? num[1] : 'TBD'}`// + (cc ? ` @ ${cc}` : '')
break;
case 'hackathon':
num = item.id.match(/^w3ph(\d+)$/)[1]
return `W3PN Hackathon #${num} ${item.city}`// + (cc ? ` @ ${cc}` : '')
break;
case 'privacy-corner':
return `Privacy Corner at `+ (item.coincidenceFull ? item.coincidenceFull : `${item.coincidence} ${year}`)
break;
}
}
function ccRenderer (item) {
let ccm = item.coincidence?.match(/\[(\w+)\]/)
let cc = ccm && ccm[1] ? ccm[1] : (item.coincidence ? item.coincidence : null)
return cc
}
function dateEnd(str, days) {
return format(addDays(new Date(str), days-1), 'yyyy-MM-dd');
}
const statuses = {
preregistration: {
title: 'Pre-registration',
color: 'text-orange-500',
},
unconfirmed: {
title: 'Planned',
color: ''
},
confirmed: {
title: 'Confirmed',
color: 'text-green-500',
},
past: {
title: 'Already happened',
color: 'text-green-800',
}
}
const status = item.confirmed
? (future ? statuses.past : statuses.confirmed)
: (item.links?.rsvp ? statuses.preregistration : statuses.unconfirmed);
import { dateFormat, dateInfo, dateEnd, nameRenderer, ccRenderer, eventStatus, getSpeaker, findExt } from '../lib/events.js';
import SpeakerList from './SpeakerList.astro';
const ext = findExt(EventsExt, item)
const status = eventStatus(item)
---
<div class="w3pn-event-item">
@ -112,7 +21,7 @@ const status = item.confirmed
</div>
<div>
<div class="">
<span class="text-white text-lg">{nameRenderer(item)}</span>
<a href={`/event/${item.id}`} class="text-white text-lg hover:underline">{nameRenderer(item)}</a>
<div class="inline-block ml-2">
{item.type === "hackathon" && <span class="text-xs ml-2 text-black bg-white px-1 py-0.5">HACKATHON</span>}
{item.type === "summit" && <span class="text-xs ml-2 text-black bg-white px-1 py-0.5">SUMMIT</span>}
@ -135,7 +44,7 @@ const status = item.confirmed
<div class="grow"></div>
{item.speakers &&
<div class="flex -space-x-3">
{item.speakers.map(spId => getSpeaker(spId)).slice(0,7).map((speaker) => (
{item.speakers.map(spId => getSpeaker(core, spId)).slice(0,7).map((speaker) => (
<div><img src={speaker.imageUrl} class="w-8 h-8 aspect-square object-fit rounded-full border-gray-800 border-2" /></div>
))}
{item.speakers.length > 7 &&
@ -169,7 +78,7 @@ const status = item.confirmed
<div>
<div>Date: <span class="text-white">{dateFormat(item.date)} {item.days ? ' - ' + dateFormat(dateEnd(item.date, item.days)) + ` (${item.days} days)` : ''}</span></div>
<div>
Place: {item.place && <span class="text-white" set:html={marked.parseInline(item.place)}></span> || "TBD"}
Venue: {item.place && <span class="text-white" set:html={marked.parseInline(item.place)}></span> || "TBD"}
{item.place && item['place-address'] &&
<span> @ </span>
<span class="text-white">{item['place-address']}</span>
@ -187,27 +96,7 @@ const status = item.confirmed
}
</div>
{item.speakers &&
<div class="mt-6 mb-6">
<h2>Speakers ({item.speakers.length})</h2>
<div class="grid grid-cols-1 gap-6 mt-4">
{item.speakers.map(spId => getSpeaker(spId)).map((speaker) => (
<div class="flex gap-4">
<div><img class="w-14 h-14 aspect-square rounded-full" src={speaker.imageUrl} /></div>
<div>
<div>
<span class="text-white">{speaker.name}</span>
{speaker.refs?.twitter &&
<span class="ml-2">(<a href={"https://twitter.com/"+speaker.refs.twitter}>@{speaker.refs.twitter}</a>)</span>
}
</div>
<div set:html={marked.parseInline(speaker.caption)} class="text-sm"></div>
</div>
</div>
))}
</div>
</div>
}
<SpeakerList {item} />
</div>
</div>
</div>

View file

@ -0,0 +1,29 @@
---
import { marked, } from 'marked';
const { item, thumbSize } = Astro.props;
import { getSpeaker } from '../lib/events.js';
import core from "../core.json";
---
{item.speakers &&
<div class="mt-6 mb-6">
<h2>Speakers ({item.speakers.length})</h2>
<div class="w3pn-speaker-list grid grid-cols-1 gap-6 mt-4">
{item.speakers.map(spId => getSpeaker(core, spId)).map((speaker) => (
<div class="flex gap-4">
<div><img class={`${thumbSize === 'big' ? 'w-16 h-16' : 'w-14 h-14'} aspect-square rounded-full`} src={speaker.imageUrl} /></div>
<div>
<div>
<span class="text-white">{speaker.name}</span>
{speaker.refs?.twitter &&
<span class="ml-2">(<a href={"https://twitter.com/"+speaker.refs.twitter}>@{speaker.refs.twitter}</a>)</span>
}
</div>
<div set:html={marked.parseInline(speaker.caption)} class="text-sm"></div>
</div>
</div>
))}
</div>
</div>
}

View file

@ -5,7 +5,7 @@ import * as config from '../config.yaml';
import * as pkg from '../../package.json';
import core from '../core.json';
import '../styles/base.css';
const {banner, title, description} = Astro.props;
const {banner, title, metaTitle, description} = Astro.props;
import cfonts from 'cfonts';
@ -21,7 +21,7 @@ function genHeading(str) {
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<meta name="description" content={description} />
<title>{title ? title + ' | ' + config.title : config.title}</title>
<title>{(metaTitle || title) ? ((metaTitle || title) + ' | ' + config.title) : config.title}</title>
<link
rel="preload"

99
src/lib/events.js Normal file
View file

@ -0,0 +1,99 @@
import { format, compareAsc, addDays, isFuture } from 'date-fns';
export function dateInfo (item) {
const isDate = item.date.match(/^\d{4}-\d{2}-\d{2}$/)
const future = isDate && !isFuture(new Date(item.date));
const dateMatch = item.date.match(/^(\d{4})/)
const year = dateMatch ? dateMatch[1] : null
return { isDate, isFuture: future, year }
}
export function dateFormat (str) {
if (str.match(/^\d{4}-\d{2}-\d{2}$/)) {
return format(new Date(str), 'MMM d, yyyy')
}
const qm = str.match(/^(\d{4})\/(\w+)$/)
if (qm) {
return `${qm[2]}, ${qm[1]}`
}
return str
}
export function dateEnd(str, days) {
return format(addDays(new Date(str), days-1), 'yyyy-MM-dd');
}
export function nameRenderer (item, full = false) {
let num;
let ccm = item.coincidence?.match(/\[(\w+)\]/)
let cc = ccm && ccm[1] ? ccm[1] : (item.coincidence ? item.coincidence : null)
const date = dateInfo(item)
switch (item.type) {
case 'summit':
num = item.id.match(/^w3ps(\d+)$/)[1]
return `Summmit ${item.city}` + (full ? ` ${date.year}`: '')// + (cc ? ` @ ${cc}` : '')
break;
case 'meetup':
num = item.id.match(/(\d+)$/)
return `Meetup ${item.city}` + (full ? ` ${date.year}`: '')// + (cc ? ` @ ${cc}` : '')
break;
case 'hackathon':
num = item.id.match(/^w3ph(\d+)$/)[1]
return `Hackathon ${item.city}` + (full ? ` ${date.year}`: '')// + (cc ? ` @ ${cc}` : '')
break;
case 'privacy-corner':
return `Privacy Corner at `+ (item.coincidenceFull ? item.coincidenceFull : `${item.coincidence} ${date.year}`)
break;
}
}
export function ccRenderer (item) {
let ccm = item.coincidence?.match(/\[(\w+)\]/)
let cc = ccm && ccm[1] ? ccm[1] : (item.coincidence ? item.coincidence : null)
return cc
}
export function eventStatus (item) {
const statuses = {
preregistration: {
title: 'Pre-registration',
color: 'text-orange-500',
},
unconfirmed: {
title: 'Planned',
color: ''
},
confirmed: {
title: 'Confirmed',
color: 'text-green-500',
},
past: {
title: 'Already happened',
color: 'text-green-800',
}
}
const date = dateInfo(item)
return item.confirmed
? (date.isDate ? statuses.past : statuses.confirmed)
: (item.links?.rsvp ? statuses.preregistration : statuses.unconfirmed);
}
export function getSpeaker (core, id) {
return core.people.find(p => p.id === id)
}
export function findExt (eventsExt, item) {
let slug = null
if (item.links?.web && item.links.web.match(/^https:\/\/lu.ma\//)) {
slug = item.links.web.match(/^https:\/\/lu.ma\/(.+)$/)[1]
}
if (item.links?.rsvp && item.links.rsvp.match(/^https:\/\/lu.ma\//)) {
slug = item.links.rsvp.match(/^https:\/\/lu.ma\/(.+)$/)[1]
}
if (!slug) {
return null
}
return eventsExt.find(ex => ex.url === slug)
}

View file

@ -0,0 +1,82 @@
---
import BaseLayout from '../../layouts/base.astro';
import SpeakerList from '../../components/SpeakerList.astro';
import core from '../../core.json';
import EventsExt from '../../events-ext.json';
import { dateFormat, dateInfo, dateEnd, nameRenderer, ccRenderer, eventStatus, findExt } from '../../lib/events.js';
import { marked } from 'marked';
const { id } = Astro.params;
export async function getStaticPaths() {
return core.events.map(event => ({ params: { id: event.id }}));
}
const item = core.events.find(event => event.id === id)
const status = eventStatus(item)
const ext = findExt(EventsExt, item)
---
<BaseLayout title={id} metaTitle={nameRenderer(item, true)}>
<div class="middle-pane-medium mt-10">
<div class="sm:flex w-full">
<div class="grow">
<h1 id="upcoming">W3PN {nameRenderer(item, true)}</h1>
<div class="flex gap-2 mb-4 text-lg">
<img src={`/flags/${item.country}.svg`} class="w-4" />
<div>
{item.city}, {item.country.toUpperCase()}
{item.coincidence &&
<span>&nbsp;- {ccRenderer(item)}</span>
}
</div>
</div>
<div>
<div>Date: <span class="text-white">{dateFormat(item.date)} {item.days ? ' - ' + dateFormat(dateEnd(item.date, item.days)) + ` (${item.days} days)` : ''}</span></div>
<div>
Venue: {item.place && <span class="text-white" set:html={marked.parseInline(item.place)}></span> || "TBD"}
{item.place && item['place-address'] &&
<span> @ </span>
<span class="text-white">{item['place-address']}</span>
}
</div>
<div>Status: <span class:list={[status.color]} class="mr-1.5 text-xs">●</span> {status.title}</div>
<div>Visitors:
{item.visitors &&
<span class="text-white">{item.visitors} people</span>
}
{!item.visitors && ext &&
<span><span class="text-white">{ext.guestCount > 0 ? (ext.guestCount + ' people') : 'n/a'}</span> {status.title === 'Pre-registration' ? 'pre-registered' : 'registered'}</span>
}
{!item.visitors && !ext &&
<span>n/a</span>
}
</div>
<div>Lead: <span class="text-white">{item.lead || 'n/a'}</span></div>
<div class="mt-4 mb-2">
{item.links?.rsvp &&
<a href={item.links.rsvp} class="button inverted"><button>{status.title === 'Pre-registration' ? 'Pre-registration' : 'Registration'}</button></a>
}
{item.links?.web &&
<a href={item.links.web} class="button inverted"><button>Website</button></a>
}
</div>
</div>
</div>
<div class="items-right">
{ext &&
<div><img src={ext.coverUrl} class="rounded border border-white/20 w-80 h-80 aspect-square" /></div>
}
</div>
</div>
<SpeakerList {item} thumbSize="big"/>
</div>
</BaseLayout>

View file

@ -69,6 +69,9 @@ for (const year of pastYears.reverse()) {
if (ev.target.tagName === "BUTTON") {
return false;
}
if (ev.target.tagName === "A") {
return false;
}
const detail = el.parentElement.parentElement.querySelector('.detail')
document.querySelectorAll('.detail:not(.hidden)').forEach(e => (detail !== e ? e.classList.add('hidden') : null));
detail.classList.toggle('hidden');

View file

@ -193,6 +193,10 @@
@apply mb-4;
}
.w3pn-speaker-list a {
@apply hover:text-white;
}
.icon {
@apply inline-block w-12 h-12;
}