From 71d32defbcb3f05e1f7dfac9fb1bf4cc14151cb1 Mon Sep 17 00:00:00 2001 From: Profile Profile Date: Mon, 16 Mar 2026 16:48:34 +0300 Subject: [PATCH] add logic exclude --- src/components/ColonPost.astro | 155 +-------------- src/components/Header/Header.astro | 2 + src/components/MainLine.astro | 130 ++----------- src/components/MainPostWidget.astro | 118 +----------- src/lib/api/posts.ts | 209 +++++++++++++-------- src/pages/articles/index.astro | 4 +- src/pages/index.astro | 53 ++++-- src/pages/news/index.astro | 16 ++ src/styles/components/colon-post.css | 141 ++++++++++++++ src/styles/components/main-post-widget.css | 110 +++++++++++ src/styles/components/mainline.css | 110 +++++++++++ src/styles/main.css | 5 +- 12 files changed, 582 insertions(+), 471 deletions(-) create mode 100644 src/styles/components/colon-post.css create mode 100644 src/styles/components/main-post-widget.css create mode 100644 src/styles/components/mainline.css diff --git a/src/components/ColonPost.astro b/src/components/ColonPost.astro index ee922db..4ea302e 100644 --- a/src/components/ColonPost.astro +++ b/src/components/ColonPost.astro @@ -1,8 +1,11 @@ --- -import { getLatestColonPost } from '@lib/api/colon-posts'; + import Author from '@components/AuthorDisplay.astro'; -const colonPost = await getLatestColonPost(); +const { + colonPost=[] +} = Astro.props; + --- {colonPost && ( @@ -42,150 +45,4 @@ const colonPost = await getLatestColonPost(); -)} - - \ No newline at end of file +)} \ No newline at end of file diff --git a/src/components/Header/Header.astro b/src/components/Header/Header.astro index dfa9640..99fc8be 100644 --- a/src/components/Header/Header.astro +++ b/src/components/Header/Header.astro @@ -9,6 +9,7 @@ let menuItems = []; ---
+
Профиль @@ -25,5 +26,6 @@ let menuItems = [];
+
diff --git a/src/components/MainLine.astro b/src/components/MainLine.astro index d4bea1a..4a4512f 100644 --- a/src/components/MainLine.astro +++ b/src/components/MainLine.astro @@ -1,128 +1,26 @@ --- + // src/components/MainLine.astro import EndnewsList from '@components/EndnewsList.astro'; import MainPostWidget from '@components/MainPostWidget.astro'; import ColonPost from '@components/ColonPost.astro'; + +const { + mainPost=[], + colonPost=[] +} = Astro.props; + ---
-
-
+
+ +
+
+ +
Правая колонка (260px)
-
- - \ No newline at end of file + \ No newline at end of file diff --git a/src/components/MainPostWidget.astro b/src/components/MainPostWidget.astro index 644fe7f..d875cef 100644 --- a/src/components/MainPostWidget.astro +++ b/src/components/MainPostWidget.astro @@ -1,11 +1,11 @@ --- -// src/components/MainPostWidget.astro -import { getLatestMainPost } from '@lib/api/main-posts'; import Author from '@components/AuthorDisplay.astro'; import CategoryBadge from '@components/CategoryBadge.astro'; // цветная плитка рубрик -const mainPost = await getLatestMainPost(); +const { + mainPost=[] +} = Astro.props; if (!mainPost) return null; @@ -88,115 +88,3 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', { {categoryName && } - diff --git a/src/lib/api/posts.ts b/src/lib/api/posts.ts index dc444db..9e42f6d 100644 --- a/src/lib/api/posts.ts +++ b/src/lib/api/posts.ts @@ -11,101 +11,156 @@ export interface AnewsPost { date: string; } -export async function getLatestPosts(first = 14, after = null) { - // Создаем уникальный ключ для кэша - const cacheKey = `latest-posts:${first}:${after || 'first-page'}`; + + +export async function getLatestPosts(first = 14, after = null, excludeIds = []) { + // Нормализуем excludeIds - работаем только с databaseId (числа или строки) + const excludeArray = Array.isArray(excludeIds) + ? excludeIds.filter(id => id != null).map(id => id.toString()) + : (excludeIds ? [excludeIds.toString()] : []); + + // Создаем уникальный ключ для кэша с учетом исключений + const excludeKey = excludeArray.length ? `exclude:${excludeArray.sort().join(',')}` : ''; + const cacheKey = `latest-posts:${first}:${after || 'first-page'}${excludeKey ? `:${excludeKey}` : ''}`; return await cache.wrap( cacheKey, async () => { - const query = ` - query GetLatestProfileArticles($first: Int!, $after: String) { - profileArticles( - first: $first - after: $after - where: { orderby: { field: DATE, order: DESC } } - ) { - pageInfo { - hasNextPage - endCursor - } - edges { - cursor - node { - id - databaseId - title - uri - date - featuredImage { - node { - sourceUrl(size: LARGE) - altText - } - } - author { - node { - id - name - firstName - lastName - avatar { - url + // Функция для выполнения запроса + const fetchPosts = async (limit, cursor) => { + const query = ` + query GetLatestProfileArticles($first: Int!, $after: String) { + profileArticles( + first: $first + after: $after + where: { orderby: { field: DATE, order: DESC } } + ) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + databaseId + title + uri + date + featuredImage { + node { + sourceUrl(size: LARGE) + altText + } + } + author { + node { + id + name + firstName + lastName + avatar { + url + } + uri + } + } + coauthors { + id + name + firstName + lastName + url + description + } + categories { + nodes { + id + name + color + slug + uri + databaseId + } + } + tags { + nodes { + id + name + slug + uri + } + } + } + } } - uri } - } - # Соавторы как массив - coauthors { - id - name - firstName - lastName - url - description - } - categories { - nodes { - id - name - color - slug - uri - databaseId - } - } - tags { - nodes { - id - name - slug - uri - } - } - } - } - } -} - `; + `; + + return await fetchGraphQL(query, { first: limit, after: cursor }); + }; - const data = await fetchGraphQL(query, { first, after }); + // Если нет исключений, просто возвращаем результат + if (excludeArray.length === 0) { + const data = await fetchPosts(first, after); + const posts = data.profileArticles?.edges?.map(edge => edge.node) || []; + + return { + posts, + pageInfo: data.profileArticles?.pageInfo || { hasNextPage: false, endCursor: null } + }; + } + + // Логика с исключениями - дозагружаем недостающие + let allPosts = []; + let currentAfter = after; + let hasMore = true; + let totalNeeded = first; - // Преобразуем edges в nodes - const posts = data.profileArticles?.edges?.map(edge => edge.node) || []; + // Продолжаем загрузку пока не наберем нужное количество или не кончатся посты + while (allPosts.length < totalNeeded && hasMore) { + // Запрашиваем с запасом, чтобы компенсировать исключения + const fetchLimit = Math.max(totalNeeded - allPosts.length + excludeArray.length, 1); + + const data = await fetchPosts(fetchLimit, currentAfter); + const edges = data.profileArticles?.edges || []; + const pageInfo = data.profileArticles?.pageInfo; + + // Фильтруем исключенные ID - сравниваем ТОЛЬКО databaseId + const newPosts = edges + .map(edge => edge.node) + .filter(node => !excludeArray.includes(node.databaseId?.toString())); + + allPosts = [...allPosts, ...newPosts]; + + // Обновляем курсор для следующей страницы + currentAfter = pageInfo?.endCursor; + hasMore = pageInfo?.hasNextPage && edges.length > 0; + + // Защита от бесконечного цикла (если что-то пошло не так) + if (edges.length === 0) break; + } + + // Обрезаем до нужного количества + const finalPosts = allPosts.slice(0, first); + + // Определяем, есть ли еще страницы + const hasNextPage = allPosts.length > first || hasMore; return { - posts, - pageInfo: data.profileArticles?.pageInfo || { hasNextPage: false, endCursor: null } + posts: finalPosts, + pageInfo: { + hasNextPage, + endCursor: currentAfter // Последний использованный курсор + } }; }, - { ttl: CACHE_TTL.POSTS } // из конфигурации + { ttl: CACHE_TTL.POSTS } ); } - export async function getPostsByCategory(slug, first = 14, after = null) { // Создаем уникальный ключ для кэша const cacheKey = `category-posts:${slug}:${first}:${after || 'first-page'}`; diff --git a/src/pages/articles/index.astro b/src/pages/articles/index.astro index 06af93a..7410292 100644 --- a/src/pages/articles/index.astro +++ b/src/pages/articles/index.astro @@ -3,9 +3,9 @@ import ContentLayout from '@layouts/ContentLayout.astro'; import ContentGrid from '@components/ContentGrid.astro'; -import { getLatestPosts } from '@api/posts.js'; +import { getLatestPosts } from '@api/posts'; -const { posts, pageInfo } = await getLatestPosts(41); // Сразу деструктурируем +const { posts, pageInfo } = await getLatestPosts(41); //ISR diff --git a/src/pages/index.astro b/src/pages/index.astro index 8d38a8d..d6deacb 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,15 +1,10 @@ --- -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'; - -const site = await getSiteInfo(); -const { posts, pageInfo } = await getLatestPosts(41); // Сразу деструктурируем +import { getSiteInfo } from "@lib/wp-api.js"; +import { getLatestPosts } from '@api/posts'; +import { getLatestMainPost } from '@lib/api/main-posts'; +import { getLatestColonPost } from '@lib/api/colon-posts'; +import { fetchWPRestGet } from "@lib/api/wp-rest-get-client"; // визуальные компоненты import MainLayout from '@layouts/MainLayout.astro'; @@ -19,6 +14,39 @@ import MainLine from '@components/MainLine.astro'; import HomeNews from "@components/HomeNews.astro"; +//import '../styles/home.css'; +import '../styles/main.css'; + + +//получаем главный пост +const mainPost = await getLatestMainPost(); +const mainPostId = mainPost?.databaseId; + +//колонка +const colonPost = await getLatestColonPost(); +const colonPostId = mainPost?.databaseId; + + +// Создаем массив исключений +const excludeIds = []; + +// Добавляем ID если они существуют +if (mainPostId) { + excludeIds.push(mainPostId); +} + +if (colonPostId) { + excludeIds.push(colonPostId); +} + + + +const site = await getSiteInfo(); +const { posts, pageInfo } = await getLatestPosts(41, null, excludeIds ); // Сразу деструктурируем + + + + //ISR export const prerender = false; --- @@ -30,7 +58,10 @@ export const prerender = false; - +excludeIds

Все новости

+ + + + \ No newline at end of file diff --git a/src/styles/components/colon-post.css b/src/styles/components/colon-post.css new file mode 100644 index 0000000..cb80949 --- /dev/null +++ b/src/styles/components/colon-post.css @@ -0,0 +1,141 @@ +/* Основная карточка */ + .colon-post-card { + background: #ececec; + border-radius: 8px; + overflow: hidden; + transition: transform 0.3s ease, box-shadow 0.3s ease; + width: 100%; + max-width: 800px; /* опционально, для демо */ + height: 200px; /* фиксированная высота карточки */ + } + + .colon-post-card:hover { + transform: translateY(-4px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + } + + /* Flex-контейнер: две части */ + .split-flex { + display: flex; + height: 100%; + width: 100%; + } + + /* ЛЕВЫЙ БЛОК: ровно 30% */ + .left-photo { + flex: 0 0 34%; /* ширина 30%, не растягивается */ + height: 100%; + background: #d4d4d4; /* фон, если нет фото */ + display: flex; + } + + .photo-link { + display: flex; + width: 100%; + height: 100%; + text-decoration: none; + } + + .photo-img { + width: 100%; + height: 100%; + object-fit: cover; /* заполняет контейнер, сохраняя пропорции и обрезаясь */ + display: block; + transition: transform 0.3s ease; + } + + .photo-img:hover { + transform: scale(1.05); /* легкий эффект при наведении */ + } + + .photo-placeholder { + width: 100%; + height: 100%; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + } + + /* ПРАВЫЙ БЛОК: 70% */ + .right-content { + flex: 1; /* занимает оставшееся место (70%) */ + height: 100%; + padding: 16px 20px; /* внутренние отступы */ + box-sizing: border-box; + display: flex; + flex-direction: column; + } + + /* Обёртка для контента, чтобы занять всю высоту и распределить пространство */ + .content-wrapper { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + } + + /* Заголовок жирным */ + .bold-title { + font-size: 1.125rem; + font-weight: 700; + line-height: 1.4; + color: #2c3e50; + margin: 0 0 8px 0; + transition: color 0.3s ease; + } + + .title-link { + text-decoration: none; + color: inherit; + } + + .title-link:hover .bold-title { + color: #3498db; + } + + /* Мета-строка: прижимаем к низу */ + .meta-line { + font-size: 0.9rem; + color: #666; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; + margin-top: auto; /* это прижимает мету к низу */ + } + + .separator { + color: #aaa; + font-weight: 300; + } + + .author :global(a) { + color: #2c3e50; + text-decoration: none; + font-weight: 500; + } + + .author :global(a:hover) { + color: #3498db; + text-decoration: underline; + } + + /* Адаптивность */ + @media (max-width: 600px) { + .colon-post-card { + height: auto; + min-height: 180px; + } + + .left-photo { + flex: 0 0 30%; + aspect-ratio: 1 / 1; /* сохраняем квадрат на мобильных */ + height: auto; + } + + .right-content { + padding: 12px 16px; + } + + .bold-title { + font-size: 1.1rem; + } + } diff --git a/src/styles/components/main-post-widget.css b/src/styles/components/main-post-widget.css new file mode 100644 index 0000000..cb3d43c --- /dev/null +++ b/src/styles/components/main-post-widget.css @@ -0,0 +1,110 @@ +/* ОСНОВНОЕ: ограничиваем ширину виджета */ +.main-post-widget { + width: 100%; + border-radius: 8px; + overflow: hidden; + background: white; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); +} + +.image-container { + position: relative; + width: 100%; + aspect-ratio: 16/9; + overflow: hidden; +} + +.image-link { + display: block; + width: 100%; + height: 100%; + text-decoration: none; + color: inherit; +} + +.post-image { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; +} + +.image-link:hover .post-image { + transform: scale(1.03); +} + +.image-placeholder { + width: 100%; + height: 100%; + background: linear-gradient(135deg, #f5f5f5, #e0e0e0); +} + +.category-badge { + position: absolute; + top: 16px; + left: 16px; + padding: 6px 12px; + border-radius: 4px; + font-size: 0.625rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: white; + z-index: 2; + line-height: 1; +} + +.content-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 24px; + background: linear-gradient(transparent, rgba(0,0,0,0.7) 70%); + color: white; + z-index: 1; +} + +.date-overlay { + font-size: 0.875rem; + opacity: 0.9; + display: block; +} + +.title-overlay { + margin: 0 0 12px 0; + font-size: 1.4rem; + 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 { + font-size: 0.875rem; + opacity: 0.9; +} + +/* Адаптивность */ +@media (max-width: 1023px) { + .main-post-widget { + border-radius: 0; + max-width: 100%; + } + + .content-overlay { + padding: 16px; + } + + .title-overlay { + font-size: 1.25rem; + } +} \ No newline at end of file diff --git a/src/styles/components/mainline.css b/src/styles/components/mainline.css new file mode 100644 index 0000000..6716392 --- /dev/null +++ b/src/styles/components/mainline.css @@ -0,0 +1,110 @@ +/* Основной контейнер */ +.three-col-block { + display: flex; + margin: 30px 0; + width: 100%; + max-width: 1200px; + margin-left: auto; + margin-right: auto; + flex-wrap: nowrap; + align-items: stretch; /* ВАЖНО: растягиваем все колонки на всю высоту */ +} + +/* ЛЕВАЯ КОЛОНКА - фиксированная */ +.left-col { + flex: 0 0 260px; + min-width: 260px; + max-width: 260px; + box-sizing: border-box; + display: flex; /* Добавляем flex для растягивания содержимого */ +} + +.left-col > :deep(*) { + width: 100%; /* Растягиваем компонент на всю ширину */ + height: 100%; /* Растягиваем компонент на всю высоту */ +} + +/* ЦЕНТРАЛЬНАЯ КОЛОНКА - гибкая */ +.center-col { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 20px; + margin: 0 20px; + box-sizing: border-box; +} + +.center-top, .center-bottom { + #flex: 1 1 0; /* ВАЖНО: оба блока занимают равную высоту */ + background: #fff; + box-sizing: border-box; + overflow: hidden; + word-wrap: break-word; + display: flex; /* Для растягивания внутреннего содержимого */ + flex-direction: column; +} + +.center-top > :deep(*), .center-bottom > :deep(*) { + width: 100%; + flex: 1; /* Растягиваем компонент на всю доступную высоту */ +} + +/* ПРАВАЯ КОЛОНКА - фиксированная */ +.right-col { + flex: 0 0 260px; + min-width: 260px; + max-width: 260px; + background: #f0f0f0; + padding: 20px; + box-sizing: border-box; + display: flex; /* Добавляем flex для растягивания содержимого */ + flex-direction: column; +} + +.right-col > :deep(*) { + width: 100%; + flex: 1; /* Растягиваем контент на всю высоту */ +} + +/* Ограничиваем контент внутри центральной колонки */ +.center-col > * { + max-width: 100%; + overflow-wrap: break-word; +} + +/* МОБИЛЬНАЯ ВЕРСИЯ */ +@media (max-width: 768px) { + .three-col-block { + flex-direction: column; + flex-wrap: wrap; + max-width: 100%; + overflow: visible; + align-items: stretch; + } + + .left-col, .center-col, .right-col { + flex: 1 1 100%; + min-width: 100%; + max-width: 100%; + width: 100%; + margin: 0 0 20px 0; + } + + .center-col { + margin: 0; + } + + .right-col{ + display: none; + } + + /* Сбрасываем flex свойства для мобильной версии */ + .left-col, .right-col { + display: block; + } + + .center-top, .center-bottom { + display: block; + } +} \ No newline at end of file diff --git a/src/styles/main.css b/src/styles/main.css index ee01261..d878467 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -3,7 +3,10 @@ @import './ContentLayout.css'; @import './header.css'; @import './mainmenu.css'; -@import './article.css'; +@import './mainmenu.css'; +@import './components/mainline.css'; +@import './components/colon-post.css'; +@import './components/main-post-widget.css'; @import './footer.css'; @import './embedded-content.css'; @import './components/ContentGrid.css';