177 lines
5.4 KiB
Svelte
177 lines
5.4 KiB
Svelte
<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>
|