mirror of
https://github.com/web3privacy/web
synced 2024-10-15 18:26:27 +02:00
event details
This commit is contained in:
parent
fb7b4d070c
commit
9281234215
7 changed files with 227 additions and 121 deletions
|
@ -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>
|
29
src/components/SpeakerList.astro
Normal file
29
src/components/SpeakerList.astro
Normal 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>
|
||||
}
|
|
@ -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
99
src/lib/events.js
Normal 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)
|
||||
}
|
82
src/pages/event/[id].astro
Normal file
82
src/pages/event/[id].astro
Normal 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> - {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>
|
|
@ -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');
|
||||
|
|
|
@ -193,6 +193,10 @@
|
|||
@apply mb-4;
|
||||
}
|
||||
|
||||
.w3pn-speaker-list a {
|
||||
@apply hover:text-white;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@apply inline-block w-12 h-12;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue