add logic exclude

This commit is contained in:
Profile Profile
2026-03-16 16:48:34 +03:00
parent 7a7df39c1b
commit 71d32defbc
12 changed files with 582 additions and 471 deletions

View File

@@ -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 && (
@@ -43,149 +46,3 @@ const colonPost = await getLatestColonPost();
</div>
</div>
)}
<style>
/* Основная карточка */
.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;
}
}
/* Если нужно точное соответствие макету, можно добавить медиа-запросы под свои нужды */
</style>

View File

@@ -9,6 +9,7 @@ let menuItems = [];
---
<header class="header" itemscope itemtype="https://schema.org/WPHeader">
<div class="top-bar">
<a href="/"><img alt="Профиль" width="249" height="55" src="https://cdn.profile.ru/wp-content/themes/profile/assets/img/profile-logo-delovoy.svg"></a>
@@ -25,5 +26,6 @@ let menuItems = [];
</div>
<MainMenu menuId={MENU_ID} />
</header>

View File

@@ -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;
---
<div class="three-col-block">
<div class="left-col"><EndnewsList /></div>
<div class="center-col">
<div class="center-top"><MainPostWidget /></div>
<div class="center-bottom"><ColonPost /></div>
<div class="center-top">
<MainPostWidget mainPost={mainPost}/>
</div>
<div class="center-bottom">
<ColonPost colonPost={colonPost}/>
</div>
</div>
<div class="right-col">Правая колонка (260px)</div>
</div>
<style>
/* Основной контейнер */
.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;
}
}
</style>

View File

@@ -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 && <meta itemprop="articleSection" content={categoryName} />}
</article>
<style>
/* ОСНОВНОЕ: ограничиваем ширину виджета */
.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;
}
}
</style>

View File

@@ -11,13 +11,23 @@ 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 fetchPosts = async (limit, cursor) => {
const query = `
query GetLatestProfileArticles($first: Int!, $after: String) {
profileArticles(
@@ -32,7 +42,6 @@ export async function getLatestPosts(first = 14, after = null) {
edges {
cursor
node {
id
databaseId
title
uri
@@ -55,7 +64,6 @@ export async function getLatestPosts(first = 14, after = null) {
uri
}
}
# Соавторы как массив
coauthors {
id
name
@@ -88,20 +96,67 @@ export async function getLatestPosts(first = 14, after = null) {
}
`;
const data = await fetchGraphQL(query, { first, after });
return await fetchGraphQL(query, { first: limit, after: cursor });
};
// Преобразуем edges в nodes
// Если нет исключений, просто возвращаем результат
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 }
};
},
{ ttl: CACHE_TTL.POSTS } // из конфигурации
);
}
// Логика с исключениями - дозагружаем недостающие
let allPosts = [];
let currentAfter = after;
let hasMore = true;
let totalNeeded = first;
// Продолжаем загрузку пока не наберем нужное количество или не кончатся посты
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: finalPosts,
pageInfo: {
hasNextPage,
endCursor: currentAfter // Последний использованный курсор
}
};
},
{ ttl: CACHE_TTL.POSTS }
);
}

View File

@@ -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

View File

@@ -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;
<!-- index.astro -->
<MainLine />
<MainLine
mainPost={mainPost}
colonPost={colonPost}
/>excludeIds
<ContentGrid

View File

@@ -1,6 +1,11 @@
---
import ContentLayout from '@layouts/ContentLayout.astro';
import ContentGrid from '@components/ContentGrid.astro';
import { getLatestAnews } from '@api/posts';
const { posts, pageInfo } = await getLatestAnews(41);
---
@@ -9,4 +14,15 @@ import ContentLayout from '@layouts/ContentLayout.astro';
description=Новости
>
<h1>Все новости</h1>
<ContentGrid
items={posts}
pageInfo={pageInfo}
type="latest"
gridColumns={3}
perLoad={11}
showCount={false}
/>
</MainLayout>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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';