teasanctuary.ru/src/routes/blog/[slug]/+page.svelte

177 lines
5.4 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
import { page } from '$app/state';
import DateWidget from '$lib/components/DateWidget.svelte';
import InfoBlock from '$lib/components/InfoBlock.svelte';
import PersonIcon from '~icons/material-symbols/person';
import type { PageData } from './$types';
import {
blogPostTypeToIconComponent,
blogPostTypeToString,
EventStatus,
postEventStatus
} from '$lib/util/Blogs';
import { onMount } from 'svelte';
import { durationHumanReadable } from '$lib/util/Dates';
import CountdownClock from '$lib/components/CountdownClock.svelte';
let { data }: { data: PageData } = $props();
const isPublic = $derived(!!data.blogPost.date);
const authors = $derived(
data.blogPost.authors == null
? []
: typeof data.blogPost.authors === 'string'
? [data.blogPost.authors]
: data.blogPost.authors
);
const type: App.BlogPostType = $derived(data.blogPost.type ?? 'article');
let eventStatus = $state(EventStatus.NotEvent);
const PostTypeIcon = $derived(blogPostTypeToIconComponent(type));
onMount(() => {
// HACK: rndtrash: NodeJS использует другой тип данных для таймаутов
let eventTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
const updateStatus = () => {
eventStatus = postEventStatus(data.blogPost);
if (eventStatus !== EventStatus.NotEvent && eventStatus !== EventStatus.IsOver) {
const endpointString =
eventStatus === EventStatus.NotStarted
? data.blogPost.dateEventFrom!
: data.blogPost.dateEventTo!;
const delay = Math.min(
new Date(endpointString).valueOf() - new Date().valueOf(),
// Из-за ограничения функции setTimeout, будем проверять максимум каждые 12 часов
12 * 60 * 60 * 1000
);
if (delay <= 0) return;
// Плюс пол секунды, чтобы анимация часов успела проиграть
eventTimeout = setTimeout(updateStatus, delay + 500);
}
};
updateStatus();
return () => clearTimeout(eventTimeout);
});
</script>
<base target="_blank" />
<svelte:head>
<meta name="twitter:card" content="summary_large_image" />
</svelte:head>
<section class="hero flex shrink-0 flex-col items-center justify-center gap-5 overflow-hidden p-4">
<div
class="flex flex-col flex-nowrap items-center justify-center gap-3 lg:flex-row lg:flex-nowrap"
>
<div class="text-left">
<h1>{data.blogPost.title}</h1>
{#if data.blogPost.description}
<h2 class="text-gray">{data.blogPost.description}</h2>
{/if}
</div>
</div>
</section>
<section
class="flex shrink-0 flex-row flex-wrap items-center justify-center p-2 font-bold {isPublic
? 'bg-amber-50 text-slate-950'
: 'bg-red-500 text-slate-50'} sm:gap-x-5"
>
<DateWidget dateString={data.blogPost.date} type="published" />
{#if data.blogPost.dateChanged}
<DateWidget dateString={data.blogPost.dateChanged} type="updated" />
{/if}
<div class="flex items-center gap-2 p-1 text-lg font-bold">
<PostTypeIcon width={28} height={28} />
<span>
{blogPostTypeToString(type)}
</span>
</div>
{#each authors as author}
<!-- TODO: rndtrash: из-за 404 не даёт собрать сайт. href="/team/{author}" -->
<a class="flex items-center gap-2 p-1 text-lg font-bold" href="#">
<PersonIcon width={28} height={28} />
<span class="underline">
{author}
</span>
</a>
{/each}
</section>
{#if type === 'event'}
<section
class="flex shrink-0 flex-col flex-wrap items-center justify-center p-2 font-bold
{eventStatus === EventStatus.NotStarted
? 'bg-amber-200'
: eventStatus === EventStatus.InProgress
? 'bg-green-600 text-slate-50'
: 'bg-slate-200'} gap-2"
>
{#if eventStatus === EventStatus.NotStarted || eventStatus === EventStatus.InProgress}
<span class="text-center text-4xl font-bold">
ДО {eventStatus === EventStatus.NotStarted ? 'НАЧАЛА' : 'КОНЦА'} ОСТАЛОСЬ
</span>
<CountdownClock
deadline={new Date(
eventStatus === EventStatus.NotStarted
? data.blogPost.dateEventFrom!
: data.blogPost.dateEventTo!
)}
/>
{:else if eventStatus === EventStatus.IsOver}
<span class="text-center text-4xl font-bold">СОБЫТИЕ ЗАВЕРШЕНО</span>
{/if}
<span class="flex flex-row flex-wrap items-center justify-center gap-2">
Событие {eventStatus === EventStatus.IsOver ? 'проводилось' : 'проводится'} с
<DateWidget
class="bg-slate-50 px-2 text-slate-950"
dateString={data.blogPost.dateEventFrom}
showTime
/>
по
<DateWidget
class="bg-slate-50 px-2 text-slate-950"
dateString={data.blogPost.dateEventTo}
showTime
/>
({durationHumanReadable(
new Date(data.blogPost.dateEventFrom!),
new Date(data.blogPost.dateEventTo!)
)})
</span>
</section>
{/if}
{#if page.data.blogPost.projects?.length > 0}
<InfoBlock>
<p>В данной заметке упоминаются наши проекты:</p>
<ul>
{#each page.data.blogPost.projects as project}
<li>{project}</li>
{/each}
</ul>
</InfoBlock>
{/if}
<article
class="prose
sm:prose-xl
prose-slate
prose-code:wrap-break-word
prose-pre:drop-shadow-md
prose-headings:font-disket
prose-headings:mb-4
prose-headings:font-bold prose-headings:text-slate-950 prose-h1:text-2xl
prose-h1:sm:text-4xl prose-h2:text-xl
prose-h2:sm:text-3xl prose-p:text-justify
mx-auto
w-5xl
p-4
text-base
text-slate-950
sm:text-xl lg:p-8"
>
<data.content />
</article>