From abcc214ef688d3929a88813bae5396e6499d19ab Mon Sep 17 00:00:00 2001 From: Profile Profile Date: Sun, 15 Feb 2026 23:05:45 +0300 Subject: [PATCH] add client rest api --- src/components/EndnewsList.astro | 428 +++++++++++++--------------- src/components/MainPostWidget.astro | 98 ++++--- src/lib/api/wp-rest-get-client.ts | 45 +++ src/pages/index.astro | 7 +- 4 files changed, 309 insertions(+), 269 deletions(-) create mode 100644 src/lib/api/wp-rest-get-client.ts diff --git a/src/components/EndnewsList.astro b/src/components/EndnewsList.astro index 86461b4..cc41e2c 100644 --- a/src/components/EndnewsList.astro +++ b/src/components/EndnewsList.astro @@ -1,275 +1,257 @@ --- import { getLatestAnews } from '../lib/api/posts.js'; +import { fetchWPRestGet } from "@/lib/api/wp-rest-get-client"; -// Функции для работы с датами + +// Даты/время function formatDate(dateString: string): string { const date = new Date(dateString); const today = new Date(); const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); - - if (date.toDateString() === today.toDateString()) { - return 'Сегодня'; - } - - if (date.toDateString() === yesterday.toDateString()) { - return 'Вчера'; - } - - return date.toLocaleDateString('ru-RU', { - day: 'numeric', - month: 'long', - year: 'numeric' - }); + + if (date.toDateString() === today.toDateString()) return 'Сегодня'; + if (date.toDateString() === yesterday.toDateString()) return 'Вчера'; + + return date.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }); } function formatTime(dateString: string): string { const date = new Date(dateString); - return date.toLocaleTimeString('ru-RU', { - hour: '2-digit', - minute: '2-digit' - }); + return date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); } function groupByDate(posts: Array<{ date: string }>) { const grouped: Record> = {}; - - posts.forEach(post => { - const date = new Date(post.date); - const dateKey = date.toISOString().split('T')[0]; - - if (!grouped[dateKey]) { - grouped[dateKey] = []; - } - - grouped[dateKey].push(post); + posts.forEach((post) => { + const d = new Date(post.date); + const key = d.toISOString().split('T')[0]; + (grouped[key] ??= []).push(post); }); - - const sortedEntries = Object.entries(grouped).sort((a, b) => - b[0].localeCompare(a[0]) - ); - - return Object.fromEntries(sortedEntries); + return Object.fromEntries(Object.entries(grouped).sort((a, b) => b[0].localeCompare(a[0]))); } -// Получаем данные при сборке (используем правильное имя функции) -const posts = await getLatestAnews(20); -const groupedPosts = groupByDate(posts); -const totalPosts = posts.length; +// Новости +const newsPosts = await getLatestAnews(20); +const groupedNews = groupByDate(newsPosts); + +// Топ-10 +type TopItem = { uri?: string; link?: string; title: string; date: string }; +type ApiResp = { top?: { items?: TopItem[] } }; + +let topPosts: TopItem[] = []; +try { + const data = await fetchWPRestGet("my/v1/news-top", { per_page: 20 }); + topPosts = (data?.top?.items ?? []).slice(0, 10); +} catch { + topPosts = []; +} + +const hasNews = newsPosts.length > 0; +const hasTop = topPosts.length > 0; + +// По умолчанию открываем вкладку, где есть данные +const defaultTab: "news" | "top" = hasTop ? "top" : "news"; --- - -
- -
-

Новости

-
+{(hasNews || hasTop) && ( +
+
+

Новости

+
- -
- {Object.entries(groupedPosts).map(([dateKey, datePosts]) => { - const dateStr = new Date(dateKey).toLocaleDateString('ru-RU', { - day: 'numeric', - month: 'long', - year: 'numeric' - }); - - return ( -
- + {/* Radio-кнопки ВНЕ .endnews-tabs для работы CSS-селекторов */} + + + + {/* Только labels в блоке табов */} +
+ + +
+ + {/* Контент: две панели, показываем нужную через :checked */} +
+
+ {Object.entries(groupedNews).map(([dateKey, datePosts]) => ( +
{formatDate(dateKey + 'T00:00:00')}
- - - - {datePosts.map((post, index) => { - const postNumber = index + 1; - const isLastInGroup = postNumber === datePosts.length; - - return ( + + {datePosts.map((post) => ( - ); - })} -
- ); - })} + ))} +
+ ))} + + +
+ {topPosts.map((post, i) => ( + + ))} + + {!hasTop &&
Топ пока пуст
} +
+
-
+)} -.endnews-link { - color: inherit; - text-decoration: none; - display: block; -} - -.endnews-link:hover { - color: #B61D1D; -} - -/* Десктоп: фиксированная высота со скроллом */ -@media (min-width: 1024px) { - .endnews-container { - height: 500px; /* Фиксированная высота */ - display: flex; - flex-direction: column; - } - - .latestnews-list { - flex: 1; - overflow-y: auto; /* Вертикальный скролл */ - overflow-x: hidden; - } - - /* Кастомный скроллбар для красоты */ - .latestnews-list::-webkit-scrollbar { - width: 6px; - } - - .latestnews-list::-webkit-scrollbar-track { - background: #f5f5f5; - border-radius: 3px; - } - - .latestnews-list::-webkit-scrollbar-thumb { - background: #d0d0d0; - border-radius: 3px; - } - - .latestnews-list::-webkit-scrollbar-thumb:hover { - background: #b0b0b0; - } -} - -/* Мобильные устройства: автоматическая высота */ -@media (max-width: 1023px) { - .endnews-container { - height: auto; /* Автоматическая высота */ - } - - .latestnews-list { - max-height: none; - overflow: visible; - } -} - -/* Планшеты (опционально) */ -@media (min-width: 768px) and (max-width: 1023px) { - .endnews-container { - height: auto; - /* или можно задать другую фиксированную высоту для планшетов */ - } -} - -/* Анимация появления скролла (опционально) */ -.latestnews-list { - scroll-behavior: smooth; -} - -/* Улучшение для touch-устройств */ -@media (hover: none) and (pointer: coarse) { - .endnews-link { - padding: 8px 0; /* Увеличение touch-зоны */ - } - - .lastnews-item { - margin: 12px; - } -} - - - - \ No newline at end of file diff --git a/src/components/MainPostWidget.astro b/src/components/MainPostWidget.astro index 5b71faf..c88f5ea 100644 --- a/src/components/MainPostWidget.astro +++ b/src/components/MainPostWidget.astro @@ -16,12 +16,13 @@ const category = mainPost?.categories?.nodes?.[0]; const categoryName = category?.name || ''; const categoryColor = category?.color || '#2271b1'; +const authorName = mainPost?.author?.node?.name || ''; + const formattedDate = postDate.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', year: 'numeric' }).replace(' г.', ''); - ---
- -
+ )} + + + {categoryName && ( +
+ {categoryName} +
+ )} + +
+
+ +
- {categoryName && ( -
- {categoryName} +

+ + {mainPost.title} + +

+ + {authorName && ( +
+
)} - -
-
- -
- -

- {mainPost.title} -

- - {authorName && ( -
- -
- )} -
- +
@@ -89,20 +92,12 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', { /* ОСНОВНОЕ: ограничиваем ширину виджета */ .main-post-widget { width: 100%; - max-width: 800px; /* Вот это ключевое правило! */ - margin: 0 auto; /* Центрируем */ border-radius: 8px; overflow: hidden; background: white; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } -.post-card-link { - display: block; - text-decoration: none; - color: inherit; -} - .image-container { position: relative; width: 100%; @@ -110,6 +105,14 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', { overflow: hidden; } +.image-link { + display: block; + width: 100%; + height: 100%; + text-decoration: none; + color: inherit; +} + .post-image { width: 100%; height: 100%; @@ -117,7 +120,7 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', { transition: transform 0.3s ease; } -.post-card-link:hover .post-image { +.image-link:hover .post-image { transform: scale(1.03); } @@ -164,7 +167,16 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', { font-size: 1.5rem; font-weight: 700; line-height: 1.3; +} + +.title-link { color: white; + text-decoration: none; + transition: opacity 0.2s ease; +} + +.title-link:hover { + opacity: 0.9; } .author-overlay { @@ -177,7 +189,7 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', { @media (max-width: 1023px) { .main-post-widget { border-radius: 0; - max-width: 100%; /* На мобильных занимает всю ширину */ + max-width: 100%; } .content-overlay { @@ -188,4 +200,4 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', { font-size: 1.25rem; } } - \ No newline at end of file + diff --git a/src/lib/api/wp-rest-get-client.ts b/src/lib/api/wp-rest-get-client.ts new file mode 100644 index 0000000..978a21d --- /dev/null +++ b/src/lib/api/wp-rest-get-client.ts @@ -0,0 +1,45 @@ +// src/lib/api/wp-rest-get-client.ts + +function joinUrl(base: string, path: string) { + return `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`; +} + +export async function fetchWPRestGet( + path: string, + query: Record = {} +): Promise { + const baseUrl = import.meta.env.WP_REST_BASE_URL; // например: https://site.ru + if (!baseUrl) { + throw new Error("WP_REST_BASE_URL is not defined in environment variables"); + } + + const endpoint = joinUrl(baseUrl, "/wp-json/"); + const url = new URL(joinUrl(endpoint, path)); + + console.log(url); + + const params = new URLSearchParams(); + for (const [k, v] of Object.entries(query)) { + if (v === undefined || v === null) continue; + if (Array.isArray(v)) v.forEach((x) => params.append(k, String(x))); + else params.set(k, String(v)); + } + url.search = params.toString(); // URLSearchParams -> querystring [web:87] + + try { + const response = await fetch(url.toString(), { method: "GET" }); + + if (!response.ok) { + throw new Error(`HTTP error ${response.status}`); + } + + // WP REST почти всегда JSON + return (await response.json()) as T; + } catch (error) { + if (error instanceof Error) { + console.error("WP REST GET request failed:", error.message); + throw error; + } + throw new Error("Unknown error in WP REST GET request"); + } +} diff --git a/src/pages/index.astro b/src/pages/index.astro index adaf1e9..11b7918 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -2,6 +2,8 @@ import { getSiteInfo } from "../lib/wp-api.js"; import { getLatestPosts } from '@api/posts.js'; +import { fetchWPRestGet } from "../lib/api/wp-rest-get-client"; + import '../styles/home.css'; @@ -14,6 +16,7 @@ import MainLayout from '@layouts/MainLayout.astro'; import ContentGrid from '@components/ContentGrid.astro'; import EndnewsList from '@components/EndnewsList.astro'; import MainLine from '@components/MainLine.astro'; +import HomeNews from "@components/HomeNews.astro"; @@ -23,10 +26,8 @@ export const prerender = false; -

{site.title}

mainline_1 - {site.description &&

{site.description}

}