Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/lib/components/ChildPageShell.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import DefaultNav from '#lib/components/DefaultNav.svelte';
import SponsorsBlock from '#lib/components/SponsorsBlock.svelte';
import type { ParsedConference, Sponsors } from '#lib/types.ts';

interface Props {
conference: ParsedConference;
params: {
conference: string;
};
sponsors?: Sponsors;
children: Snippet;
}

let { conference, params, sponsors, children }: Props = $props();
</script>

<DefaultNav {conference} {params} />
{@render children()}
{#if sponsors}
<SponsorsBlock {sponsors} />
{/if}
46 changes: 46 additions & 0 deletions src/lib/components/GenericPage.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script lang="ts">
import IntroAndEmailSignupBlock from '#lib/components/IntroAndEmailSignupBlock.svelte';
import LocationBlock from '#lib/components/LocationBlock.svelte';
import RichTextBlock from '#lib/components/RichTextBlock.svelte';
import ScheduleBlock from '#lib/components/ScheduleBlock.svelte';
import SponsorsBlock from '#lib/components/SponsorsBlock.svelte';
import TextWithButtonsBlock from '#lib/components/TextWithButtonsBlock.svelte';
import { getPageBlocks } from '#lib/pageBlocks.ts';
import type {
Page,
ParsedConference,
Schedule,
Sponsors,
} from '#lib/types.ts';

interface Props {
params: {
conference: string;
slug?: string[];
};
conference: ParsedConference;
page: Page;
schedule?: Schedule;
sponsors?: Sponsors;
}

let { params, conference, page, schedule, sponsors }: Props = $props();

const blocks = $derived(getPageBlocks(page));
</script>

{#each blocks as block (block.id)}
{#if block.contentType === 'introAndEmailSignupBlock'}
<IntroAndEmailSignupBlock {params} />
{:else if block.contentType === 'scheduleBlock'}
<ScheduleBlock {conference} {schedule} />
{:else if block.contentType === 'locationBlock'}
<LocationBlock />
{:else if block.contentType === 'sponsorsBlock'}
<SponsorsBlock {sponsors} />
{:else if block.contentType === 'richText'}
<RichTextBlock {block} />
{:else if block.contentType === 'textWithButtons'}
<TextWithButtonsBlock {block} />
{/if}
{/each}
85 changes: 85 additions & 0 deletions src/lib/components/OveItEmbed.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script lang="ts">
/* global HTMLIFrameElement, MessageEvent, window */
import { onMount } from 'svelte';
import { buildOveItIframeState } from '#lib/oveit.ts';

interface Props {
embedUrl: string;
}

let { embedUrl }: Props = $props();

let iframe: HTMLIFrameElement | undefined = $state();
let iframeSrc: string | undefined = $state();
let embedId: string | undefined = $state();

function sendInfo(time: number) {
iframe?.contentWindow?.postMessage(
{
type: 'info',
referrer: window.location.href,
embedID: embedId,
time,
},
'*',
);
}

function resizeIframe(height: unknown) {
if (!iframe) {
return;
}

const parsedHeight = Number(height);
iframe.style.height = `${Number.isNaN(parsedHeight) ? 800 : parsedHeight}px`;
}

$effect(() => {
const iframeState = buildOveItIframeState(
embedUrl,
window.location.hash,
);
embedId = iframeState?.embedId;
iframeSrc = iframeState?.iframeUrl.toString();
});

onMount(() => {
function onWindowMessage(event: MessageEvent) {
if (event.source !== iframe?.contentWindow) {
return;
}

switch (event.data?.type) {
case 'resize':
resizeIframe(event.data.height);
break;
case 'info':
sendInfo(event.data.time);
break;
}
}

function onWindowResize() {
iframe?.contentWindow?.postMessage({ type: 'resize' }, '*');
}

window.addEventListener('message', onWindowMessage);
window.addEventListener('resize', onWindowResize);

return () => {
window.removeEventListener('message', onWindowMessage);
window.removeEventListener('resize', onWindowResize);
};
});
</script>

{#if iframeSrc}
<iframe
bind:this={iframe}
title="Registration"
class="w-full overflow-hidden"
src={iframeSrc}
></iframe>
{:else}
Loading...
{/if}
128 changes: 128 additions & 0 deletions src/lib/components/SpeakerDetailPage.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<script lang="ts">
import {
faInstagram,
faLinkedin,
faXTwitter,
type IconDefinition,
} from '@fortawesome/free-brands-svg-icons';
import { faGlobe } from '@fortawesome/free-solid-svg-icons';
import CmsImage from '#lib/components/CmsImage.svelte';
import FontAwesomeIcon from '#lib/components/FontAwesomeIcon.svelte';
import RichText from '#lib/components/RichText.svelte';
import SessionCard from '#lib/components/SessionCard.svelte';
import { imageUrl } from '#lib/imageUrl.ts';
import type { ParsedSession, Speaker } from '#lib/types.ts';

interface Props {
speaker: Speaker;
sessions: ParsedSession[];
}

interface SpeakerLink {
url: string;
icon: IconDefinition;
title: string;
}

let { speaker, sessions }: Props = $props();

const bio = $derived(speaker.properties?.bio);
const jobTitle = $derived(speaker.properties?.jobTitle);
const image = $derived(speaker.properties?.profileImage?.[0]);
const links = $derived(getSpeakerLinks(speaker));

function getSpeakerLinks(speaker: Speaker): SpeakerLink[] {
const { websiteUrl, instagramUrl, linkedInUrl, xTwitterUrl } =
speaker.properties ?? {};

const links: SpeakerLink[] = [];

if (websiteUrl) {
links.push({
url: websiteUrl,
icon: faGlobe,
title: `${speaker.name} Website`,
});
}

if (xTwitterUrl) {
links.push({
url: xTwitterUrl,
icon: faXTwitter,
title: `${speaker.name} Twitter`,
});
}

if (linkedInUrl) {
links.push({
url: linkedInUrl,
icon: faLinkedin,
title: `${speaker.name} LinkedIn`,
});
}

if (instagramUrl) {
links.push({
url: instagramUrl,
icon: faInstagram,
title: `${speaker.name} Instagram`,
});
}

return links;
}
</script>

<section class="bg-gray-100 py-14">
<div class="content-container">
<div class="flex flex-wrap flex-col md:flex-row-reverse">
<div class="w-full mb-5 md:mb-0 md:w-1/3">
{#if image?.url}
<CmsImage
src={imageUrl(image.url, { width: 500, height: 560 })}
height={image.height ?? 200}
width={image.width ?? 300}
alt={`${speaker.name} profile image`}
class="mx-auto block w-full max-md:max-w-[400px]"
/>
{/if}
</div>
<div class="w-full md:w-2/3 md:pr-5">
<h1 class="text-lg md:text-3xl mb-4">
<span class="font-bold">{speaker.name}</span>{jobTitle
? `: ${jobTitle}`
: ''}
</h1>
{#if bio?.markup}
<RichText markup={bio.markup} />
{/if}
<div class="mt-4 flex flex-wrap space-x-3">
{#each links as { title, url, icon } (title)}
<a
href={url}
target="_blank"
{title}
class="text-primary text-2xl"
>
<FontAwesomeIcon {icon} />
</a>
{/each}
</div>
</div>
</div>
</div>
</section>
{#if sessions.length > 0}
<section class="py-14">
<div class="content-container">
<h2 class="text-lg md:text-3xl font-thin mb-5">Sessions</h2>
{#each sessions as session (session.id)}
<SessionCard
{session}
disableSpeakerLinks={true}
class="mb-5 last:mb-0"
/>
{/each}
</div>
</section>
{/if}
8 changes: 5 additions & 3 deletions src/lib/head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export type SharedHead = {
description?: string;
openGraph: {
title: string;
image: Required<SharedHeadImage>;
image: SharedHeadImage & { url: string };
};
twitter: {
card: 'summary_large_image';
title: string;
image: Required<SharedHeadImage>;
image: SharedHeadImage & { url: string };
};
icon: {
href: string;
Expand Down Expand Up @@ -83,7 +83,9 @@ export function buildSharedHead({
siteUrl,
metadata?.openGraph?.image?.url ?? DEFAULT_OG_IMAGE_PATH,
),
type: metadata?.openGraph?.image?.type ?? 'image/jpeg',
type:
metadata?.openGraph?.image?.type ??
(metadata?.openGraph?.image ? undefined : 'image/jpeg'),
width: metadata?.openGraph?.image?.width ?? 1200,
height: metadata?.openGraph?.image?.height ?? 630,
};
Expand Down
32 changes: 32 additions & 0 deletions src/lib/oveit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { parseUrl } from '#lib/util.ts';

export interface OveItIframeState {
embedId?: string;
iframeUrl: URL;
}

export function buildOveItIframeState(
embedUrl: string,
locationHash = '',
): OveItIframeState | undefined {
const iframeUrl = parseUrl(embedUrl);

if (!iframeUrl) {
return;
}

const embedId = iframeUrl.searchParams.get('id') ?? undefined;

if (embedId && locationHash) {
const hash = locationHash.startsWith('#')
? locationHash.substring(1)
: locationHash;
const parts = hash.split('/');

if (parts.length >= 2 && parts[0] === embedId && parts[1]) {
iframeUrl.searchParams.set('next', parts[1]);
}
}

return { embedId, iframeUrl };
}
13 changes: 13 additions & 0 deletions src/lib/pageBlocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { ContentBlock } from '#lib/types.ts';

export function getPageBlocks(page: {
properties?: {
blocks?: { items?: { content?: ContentBlock }[] } | null;
} | null;
}): ContentBlock[] {
return (
page.properties?.blocks?.items?.flatMap((item) =>
item.content ? [item.content] : [],
) ?? []
);
}
Loading
Loading