teasanctuary.ru/src/lib/components/BlogCard.svelte

167 lines
4.7 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" module>
export enum BlogCardSize {
Short,
Long,
Both
}
</script>
<script lang="ts">
import {
BLOG_POST_FRESHNESS_MILLIS,
blogPostTypeToIcon,
blogPostTypeToString
} from '$lib/util/Blogs';
import DateWidget from '$lib/components/DateWidget.svelte';
import Icon from '@iconify/svelte';
import { onMount } from 'svelte';
let {
post,
size = BlogCardSize.Both,
fullHeight = false
}: { post: App.BlogPost; size: BlogCardSize; fullHeight: boolean } = $props();
const type: App.BlogPostType = post.type ?? 'article';
let isPostNew = $state(false);
let isPostUpdated = $state(false);
let isPostFresh = $derived(isPostNew || isPostUpdated);
// TODO: rndtrash: события и их актуальность
onMount(() => {
// rndtrash: Выполняем проверки на клиенте, чтобы плашка не скомпилировалась и потом не потеряла актуальность
const dateNow = new Date().valueOf();
isPostNew = dateNow - new Date(post.date!).valueOf() <= BLOG_POST_FRESHNESS_MILLIS;
isPostUpdated =
post.dateChanged != null &&
dateNow - new Date(post.dateChanged).valueOf() <= BLOG_POST_FRESHNESS_MILLIS;
});
/**
* rndtrash: пришлось дублировать классы с модификатором и без, потому что Tailwind просто не понимает,
* когда его классы склеивают из нескольких частей
*/
/**
* Возвращает список классов для полноразмерной плашки.
*
* Возвращает пустую строку, если плашка может быть только короткой
* @param classes
* @param modClasses
*/
function fullClass(classes: string, modClasses: string): string {
if (size === BlogCardSize.Short) return '';
return size === BlogCardSize.Both ? modClasses : classes;
}
/**
* Возвращает список классов для короткой карточки.
*
* Возвращает пустую строку, если плашка может быть только полноразмерной
* @param classes
* @param modClasses
*/
function shortClass(classes: string, modClasses?: string): string {
if (size === BlogCardSize.Long) return '';
return size === BlogCardSize.Both && modClasses ? modClasses : classes;
}
</script>
<a
href="/blog/{post.slug}"
class="blog-card
{fullHeight ? 'min-h-full' : ''}
{isPostUpdated ? 'updated' : isPostNew ? 'new' : ''}
{shortClass('flex-col justify-baseline')}
{fullClass('flex-row justify-stretch', 'sm:flex-row sm:justify-stretch')}"
>
<div
class="relative w-full basis-auto overflow-hidden
{shortClass('h-32 md:h-48')}
{fullClass('h-auto basis-1/3', 'sm:h-auto sm:basis-1/3')}"
>
{#if post.thumbnail}
<img
class="thumbnail"
src={`/blog/${post.slug}/${post.thumbnail}`}
alt={post.thumbnailAlt ?? 'Миниатюра поста'}
/>
{/if}
{#if isPostFresh}
<div class="toast {fullClass('hidden', 'sm:hidden')}">
{#if isPostUpdated}
ОБНОВЛЕНО
{:else}
НОВОЕ
{/if}
</div>
{/if}
</div>
<div class="flex w-full flex-col justify-center p-4 break-words">
<div class="flex flex-row flex-wrap justify-start gap-4 pb-2">
<DateWidget
class={post.dateChanged ? shortClass('hidden', 'not-sm:hidden') : ''}
dateString={post.date}
type="published"
highlight={isPostNew && !isPostUpdated}
/>
{#if post.dateChanged}
<DateWidget
dateString={post.dateChanged}
type="updated"
highlight={isPostUpdated}
/>
{/if}
<div class="flex items-center gap-2 p-1 text-lg font-bold">
<Icon icon={blogPostTypeToIcon(type)} width={28} height={28} />
<span class={shortClass('hidden', 'not-md:hidden')}>
{blogPostTypeToString(type)}
</span>
</div>
</div>
<h2 class="text-3xl font-bold">{post.title}</h2>
{#if post.description}
<p>{post.description}</p>
{/if}
</div>
</a>
<style>
@import '$src/app.css';
.blog-card {
@apply flex w-full max-w-5xl overflow-hidden rounded-lg bg-slate-100 text-slate-950 drop-shadow-xl transition-all hover:drop-shadow-2xl;
.toast {
@apply absolute top-0 right-0 rounded-bl-lg p-2 font-bold text-slate-50;
}
&.updated {
box-shadow: 0 0 0 4px var(--color-purple-600);
.toast {
@apply bg-purple-600;
}
}
&.new {
box-shadow: 0 0 0 4px var(--color-amber-600);
.toast {
@apply bg-amber-600;
}
}
img.thumbnail {
@apply absolute h-full w-full object-cover transition-transform;
transform: scale(1);
}
&:hover img.thumbnail {
transform: scale(1.1);
}
}
</style>