ДобавлениеÐ Яндекс верификации
This commit is contained in:
parent
aa40fac43b
commit
2e892cccfb
17 changed files with 190 additions and 205 deletions
|
|
@ -3,7 +3,8 @@ import { MONTHS } from "@lib/constants";
|
|||
import NumberedContent from "./NumberedContent.astro";
|
||||
import AuthorConsultation from "./AuthorConsultation.astro";
|
||||
|
||||
export interface Props {
|
||||
// Определяем интерфейс пропсов
|
||||
interface Props {
|
||||
title: string;
|
||||
excerpt: string;
|
||||
author: {
|
||||
|
|
@ -30,6 +31,7 @@ function formatDate(dateInput: string | Date): string {
|
|||
return `${date.getDate()} ${MONTHS[date.getMonth()]} ${date.getFullYear()} года`;
|
||||
}
|
||||
|
||||
// Деструктуризация пропсов через Astro.props с явной типизацией
|
||||
const {
|
||||
title,
|
||||
excerpt,
|
||||
|
|
@ -43,101 +45,101 @@ const {
|
|||
postId,
|
||||
likes = 0,
|
||||
dislikes = 0,
|
||||
} = Astro.props;
|
||||
} = Astro.props as Props;
|
||||
|
||||
// дата поста
|
||||
const formattedPublishDate = typeof publishDate === "string"
|
||||
? publishDate
|
||||
: formatDate(publishDate);
|
||||
// Дата поста
|
||||
const formattedPublishDate = typeof publishDate === "string"
|
||||
? publishDate
|
||||
: formatDate(publishDate);
|
||||
---
|
||||
|
||||
<article class="min-h-screen bg-gray-50">
|
||||
{
|
||||
featuredImage && (
|
||||
<div class="relative w-full h-64 md:h-96 lg:h-125 overflow-hidden">
|
||||
<img
|
||||
src={featuredImage}
|
||||
alt={title}
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Затемнение для контраста текста */}
|
||||
<div class="absolute inset-0 bg-linear-to-t from-black/75 via-black/70 to-black/40" />
|
||||
|
||||
{/* Декоративные элементы */}
|
||||
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
||||
{/* Золотая линия сверху */}
|
||||
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-24 h-1 bg-linear-to-r from-transparent via-[var(--color-gold)] to-transparent opacity-80"></div>
|
||||
<!-- Уголки -->
|
||||
<div class="absolute top-8 left-8 w-16 h-16 border-t-2 border-l-2 border-gold/40 rounded-tl-lg"></div>
|
||||
<div class="absolute top-8 right-8 w-16 h-16 border-t-2 border-r-2 border-gold/40 rounded-tr-lg"></div>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-0 left-0 right-0 p-6 md:p-12">
|
||||
<div class="max-w-4xl mx-auto relative z-10">
|
||||
<!-- Категория/тег -->
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
featuredImage && (
|
||||
<div class="relative w-full h-64 md:h-96 lg:h-125 overflow-hidden">
|
||||
<img
|
||||
src={featuredImage}
|
||||
alt={title}
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Затемнение для контраста текста */}
|
||||
<div class="absolute inset-0 bg-linear-to-t from-black/75 via-black/70 to-black/40" />
|
||||
|
||||
{/* Декоративные элементы */}
|
||||
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
||||
{/* Золотая линия сверху */}
|
||||
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-24 h-1 bg-linear-to-r from-transparent via-[var(--color-gold)] to-transparent opacity-80"></div>
|
||||
<!-- Уголки -->
|
||||
<div class="absolute top-8 left-8 w-16 h-16 border-t-2 border-l-2 border-gold/40 rounded-tl-lg"></div>
|
||||
<div class="absolute top-8 right-8 w-16 h-16 border-t-2 border-r-2 border-gold/40 rounded-tr-lg"></div>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-0 left-0 right-0 p-6 md:p-12">
|
||||
<div class="max-w-4xl mx-auto relative z-10">
|
||||
<!-- Категория/тег -->
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="px-4 py-1.5 bg-gold/90 backdrop-blur-sm text-white text-xs font-bold uppercase tracking-wider rounded-full shadow-lg">
|
||||
Статья
|
||||
</span>
|
||||
<div class="h-px w-12 bg-linear-to-r from-[var(--color-gold)] to-transparent opacity-60"></div>
|
||||
</div>
|
||||
|
||||
<!-- Заголовок -->
|
||||
<h1 class="text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-extrabold text-white mb-6 leading-tight drop-shadow-2xl">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<!-- Декоративная линия под заголовком -->
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="h-1 w-20 bg-linear-to-r from-[var(--color-gold)] to-[var(--color-gold)]/60 rounded-full"></div>
|
||||
<div class="h-1 w-1 bg-[var(--color-gold)] rounded-full"></div>
|
||||
<div class="h-1 w-1 bg-[var(--color-gold)] rounded-full"></div>
|
||||
<div class="h-1 w-1 bg-[var(--color-gold)] rounded-full"></div>
|
||||
<div class="h-px w-12 bg-linear-to-r from-[var(--color-gold)] to-transparent opacity-60"></div>
|
||||
</div>
|
||||
|
||||
<!-- Заголовок -->
|
||||
<h1 class="text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-extrabold text-white mb-6 leading-tight drop-shadow-2xl">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<!-- Декоративная линия под заголовком -->
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="h-1 w-20 bg-linear-to-r from-[var(--color-gold)] to-[var(--color-gold)]/60 rounded-full"></div>
|
||||
<div class="h-1 w-1 bg-[var(--color-gold)] rounded-full"></div>
|
||||
<div class="h-1 w-1 bg-[var(--color-gold)] rounded-full"></div>
|
||||
<div class="h-1 w-1 bg-[var(--color-gold)] rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
||||
<main class="lg:col-span-8">
|
||||
{
|
||||
!featuredImage && (
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 mb-4 leading-tight">
|
||||
{title}
|
||||
</h1>
|
||||
<p class="text-xl text-gray-600 leading-relaxed">{excerpt}</p>
|
||||
</div>
|
||||
)
|
||||
!featuredImage && (
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 mb-4 leading-tight">
|
||||
{title}
|
||||
</h1>
|
||||
<p class="text-xl text-gray-600 leading-relaxed">{excerpt}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div class="flex flex-col gap-4 mb-8 pb-8 border-b border-gray-200">
|
||||
<div class="flex justify-between items-center">
|
||||
<time class="flex items-center gap-1 text-sm text-gray-500">
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
></path></svg
|
||||
></path></svg
|
||||
>
|
||||
{formattedPublishDate}
|
||||
</time>
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500">
|
||||
<svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
|
|
@ -149,78 +151,78 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
</div>
|
||||
|
||||
{
|
||||
tags.length > 0 && (
|
||||
<div class="flex flex-wrap gap-2 mb-8">
|
||||
{tags.map((tag) => (
|
||||
<span class="px-3 py-1 bg-blue-50 text-blue-600 text-sm font-medium rounded-full hover:bg-blue-100 transition-colors cursor-pointer">
|
||||
tags.length > 0 && (
|
||||
<div class="flex flex-wrap gap-2 mb-8">
|
||||
{tags.map((tag) => (
|
||||
<span class="px-3 py-1 bg-blue-50 text-blue-600 text-sm font-medium rounded-full hover:bg-blue-100 transition-colors cursor-pointer">
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
tableOfContents.length > 0 && (
|
||||
<details
|
||||
open
|
||||
class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-8 group"
|
||||
>
|
||||
<summary class="flex items-center justify-between cursor-pointer list-none font-semibold text-gray-900 text-lg select-none">
|
||||
tableOfContents.length > 0 && (
|
||||
<details
|
||||
open
|
||||
class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-8 group"
|
||||
>
|
||||
<summary class="flex items-center justify-between cursor-pointer list-none font-semibold text-gray-900 text-lg select-none">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg
|
||||
class="w-5 h-5 text-blue-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
class="w-5 h-5 text-blue-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h7"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h7"
|
||||
/>
|
||||
</svg>
|
||||
Содержание
|
||||
</span>
|
||||
<svg
|
||||
class="w-5 h-5 text-gray-400 transition-transform group-open:rotate-180"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</summary>
|
||||
<div class="mt-4 pt-4 border-t border-gray-100">
|
||||
<ol class="space-y-2">
|
||||
{tableOfContents.map((item, index) => (
|
||||
<li>
|
||||
<a
|
||||
href={`#${item.id}`}
|
||||
class="flex items-start gap-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 p-2 rounded-lg transition-colors group/item"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 text-gray-400 transition-transform group-open:rotate-180"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</summary>
|
||||
<div class="mt-4 pt-4 border-t border-gray-100">
|
||||
<ol class="space-y-2">
|
||||
{tableOfContents.map((item, index) => (
|
||||
<li>
|
||||
<a
|
||||
href={`#${item.id}`}
|
||||
class="flex items-start gap-2 text-gray-700 hover:text-blue-600 hover:bg-blue-50 p-2 rounded-lg transition-colors group/item"
|
||||
>
|
||||
<span class="shrink-0 w-6 h-6 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-sm font-medium group-hover/item:bg-blue-200">
|
||||
{index + 1}
|
||||
</span>
|
||||
<span class="leading-tight">{item.title}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</details>
|
||||
)
|
||||
<span class="leading-tight">{item.title}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</details>
|
||||
)
|
||||
}
|
||||
|
||||
<!-- ТЕЛО СТАТЬИ -->
|
||||
<div
|
||||
class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8 md:p-10"
|
||||
class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8 md:p-10"
|
||||
>
|
||||
<NumberedContent tableOfContents={tableOfContents}>
|
||||
<slot />
|
||||
|
|
@ -228,18 +230,18 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
</div>
|
||||
|
||||
<blockquote
|
||||
class="relative p-8 my-8 bg-linear-to-r from-blue-50 to-indigo-50 rounded-2xl border-l-4 border-blue-500"
|
||||
class="relative p-8 my-8 bg-linear-to-r from-blue-50 to-indigo-50 rounded-2xl border-l-4 border-blue-500"
|
||||
>
|
||||
<svg
|
||||
class="absolute top-4 left-4 w-8 h-8 text-blue-200"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
class="absolute top-4 left-4 w-8 h-8 text-blue-200"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z"
|
||||
></path></svg
|
||||
></path></svg
|
||||
>
|
||||
<p
|
||||
class="relative text-lg md:text-xl text-gray-800 font-medium italic pl-8"
|
||||
class="relative text-lg md:text-xl text-gray-800 font-medium italic pl-8"
|
||||
>
|
||||
"Правовая грамотность — лучшая защита ваших интересов в любой
|
||||
жизненной ситуации."
|
||||
|
|
@ -249,28 +251,28 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
<!-- Блок голосования -->
|
||||
<div class="mt-6 mb-8 flex justify-center">
|
||||
<div
|
||||
class="post-likes-widget"
|
||||
data-post-id={postId}
|
||||
data-initial-likes={likes}
|
||||
data-initial-dislikes={dislikes}
|
||||
class="post-likes-widget"
|
||||
data-post-id={postId}
|
||||
data-initial-likes={likes}
|
||||
data-initial-dislikes={dislikes}
|
||||
>
|
||||
<div class="vote-group">
|
||||
<button
|
||||
type="button"
|
||||
class="vote-btn like-btn"
|
||||
aria-label="Нравится"
|
||||
type="button"
|
||||
class="vote-btn like-btn"
|
||||
aria-label="Нравится"
|
||||
>
|
||||
<svg
|
||||
class="thumb-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="thumb-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
|
||||
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
|
@ -281,21 +283,21 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
|
||||
<div class="vote-group">
|
||||
<button
|
||||
type="button"
|
||||
class="vote-btn dislike-btn"
|
||||
aria-label="Не нравится"
|
||||
type="button"
|
||||
class="vote-btn dislike-btn"
|
||||
aria-label="Не нравится"
|
||||
>
|
||||
<svg
|
||||
class="thumb-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="thumb-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
|
||||
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
|
@ -304,49 +306,45 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
// Инициализация виджета голосования
|
||||
(function() {
|
||||
const widgets = document.querySelectorAll('.post-likes-widget');
|
||||
|
||||
|
||||
widgets.forEach(widget => {
|
||||
const postId = widget.getAttribute('data-post-id');
|
||||
const likeBtn = widget.querySelector('.like-btn');
|
||||
const dislikeBtn = widget.querySelector('.dislike-btn');
|
||||
const likeCount = widget.querySelector('.like-count');
|
||||
const dislikeCount = widget.querySelector('.dislike-count');
|
||||
|
||||
|
||||
let likes = parseInt(widget.getAttribute('data-initial-likes') || '0');
|
||||
let dislikes = parseInt(widget.getAttribute('data-initial-dislikes') || '0');
|
||||
let userVote = null;
|
||||
let userVote: string | null = null;
|
||||
let isLoading = false;
|
||||
let isDisabled = false; // Блокировка кнопок
|
||||
|
||||
let isDisabled = false;
|
||||
|
||||
console.log('[PostLikes] Инициализация:', { postId, likes, dislikes });
|
||||
|
||||
// Блокировка/разблокировка кнопок
|
||||
function setButtonsDisabled(disabled) {
|
||||
|
||||
function setButtonsDisabled(disabled: boolean) {
|
||||
isDisabled = disabled;
|
||||
if (likeBtn) likeBtn.disabled = disabled;
|
||||
if (dislikeBtn) dislikeBtn.disabled = disabled;
|
||||
widget.style.opacity = disabled ? '0.6' : '1';
|
||||
widget.style.pointerEvents = disabled ? 'none' : 'auto';
|
||||
if (likeBtn) (likeBtn as HTMLButtonElement).disabled = disabled;
|
||||
if (dislikeBtn) (dislikeBtn as HTMLButtonElement).disabled = disabled;
|
||||
(widget as HTMLElement).style.opacity = disabled ? '0.6' : '1';
|
||||
(widget as HTMLElement).style.pointerEvents = disabled ? 'none' : 'auto';
|
||||
}
|
||||
|
||||
// Загрузка реальных данных
|
||||
|
||||
async function loadVotes() {
|
||||
if (!postId) return;
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/likes?post_id=${encodeURIComponent(postId)}`);
|
||||
console.log('[PostLikes] Ответ API:', response.status);
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const data = await response.json() as { likes: number; dislikes: number; userVote: string | null };
|
||||
console.log('[PostLikes] Данные:', data);
|
||||
|
||||
|
||||
likes = data.likes || 0;
|
||||
dislikes = data.dislikes || 0;
|
||||
userVote = data.userVote;
|
||||
|
|
@ -356,24 +354,20 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
console.error('[PostLikes] Ошибка загрузки:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Голосование
|
||||
async function handleVote(action) {
|
||||
// Двойная проверка блокировки
|
||||
|
||||
async function handleVote(action: 'like' | 'dislike') {
|
||||
if (isLoading || isDisabled) {
|
||||
console.log('[PostLikes] Заблокировано, пропускаем');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log('[PostLikes] Голосование:', action);
|
||||
|
||||
|
||||
if (!postId) return;
|
||||
|
||||
// Блокируем кнопки СРАЗУ
|
||||
|
||||
isLoading = true;
|
||||
setButtonsDisabled(true);
|
||||
|
||||
// Оптимистичное обновление
|
||||
|
||||
if (action === 'like') {
|
||||
if (userVote === 'like') {
|
||||
likes = Math.max(0, likes - 1);
|
||||
|
|
@ -400,7 +394,7 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
}
|
||||
}
|
||||
updateUI();
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/likes', {
|
||||
method: 'POST',
|
||||
|
|
@ -408,43 +402,39 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
credentials: 'include',
|
||||
body: JSON.stringify({ post_id: postId, vote_type: action }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const data = await response.json() as { error?: string; likes?: number; dislikes?: number; userVote?: string | null };
|
||||
console.log('[PostLikes] Ответ сервера:', response.status, data);
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
alert('Для голосования необходимо войти в систему');
|
||||
loadVotes(); // Откат
|
||||
loadVotes();
|
||||
return;
|
||||
}
|
||||
throw new Error(data.error || 'Ошибка');
|
||||
}
|
||||
|
||||
// Обновляем реальными данными с сервера
|
||||
|
||||
likes = data.likes || 0;
|
||||
dislikes = data.dislikes || 0;
|
||||
userVote = data.userVote;
|
||||
userVote = data.userVote || null;
|
||||
updateUI();
|
||||
|
||||
|
||||
console.log('[PostLikes] Успешно:', { likes, dislikes, userVote });
|
||||
} catch (error) {
|
||||
console.error('[PostLikes] Ошибка:', error);
|
||||
alert('Не удалось сохранить голос');
|
||||
loadVotes(); // Откат
|
||||
loadVotes();
|
||||
} finally {
|
||||
isLoading = false;
|
||||
// НЕ разблокируем кнопки - голосование окончено
|
||||
// Кнопки остаются заблокированными для повторного голосования
|
||||
console.log('[PostLikes] Голосование завершено');
|
||||
}
|
||||
}
|
||||
|
||||
// Обновление UI
|
||||
|
||||
function updateUI() {
|
||||
if (likeCount) likeCount.textContent = likes.toString();
|
||||
if (dislikeCount) dislikeCount.textContent = dislikes.toString();
|
||||
|
||||
|
||||
if (likeBtn && dislikeBtn) {
|
||||
if (userVote === 'like') {
|
||||
likeBtn.classList.add('active');
|
||||
|
|
@ -458,21 +448,16 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Обработчики
|
||||
|
||||
likeBtn?.addEventListener('click', () => handleVote('like'));
|
||||
dislikeBtn?.addEventListener('click', () => handleVote('dislike'));
|
||||
|
||||
// Загружаем реальные данные
|
||||
|
||||
loadVotes();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Боковой блок -->
|
||||
<aside class="lg:col-span-4">
|
||||
<AuthorConsultation author={author} />
|
||||
</aside>
|
||||
|
|
@ -517,7 +502,6 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Лайк */
|
||||
.vote-btn.like-btn:hover {
|
||||
border-color: #86efac;
|
||||
background: #f0fdf4;
|
||||
|
|
@ -530,7 +514,6 @@ const formattedPublishDate = typeof publishDate === "string"
|
|||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Дизлайк */
|
||||
.vote-btn.dislike-btn:hover {
|
||||
border-color: #fca5a5;
|
||||
background: #fef2f2;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ interface Props {
|
|||
pagePath?: string;
|
||||
blogPostTitle?: string;
|
||||
bodyClass?: string;
|
||||
noContainer?: boolean; // Новый проп для отключения контейнера
|
||||
noContainer?: boolean;
|
||||
}
|
||||
|
||||
const props = Astro.props || {};
|
||||
|
|
@ -46,6 +46,8 @@ if (Astro.request) {
|
|||
<title>{title}</title>
|
||||
{description ? <meta name="description" content={description} /> : null}
|
||||
{canonicalLink ? <link rel="canonical" href={canonicalLink} /> : null}
|
||||
<!-- yandex verification -->
|
||||
<meta name="yandex-verification" content="b10d32bf1e46a882" />
|
||||
</head>
|
||||
<body class={`${props.bodyClass || "bg-gray-50"}`}>
|
||||
<Header />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue