diff --git a/src/components/ContentGrid.astro b/src/components/ContentGrid.astro index b265556..71f812e 100644 --- a/src/components/ContentGrid.astro +++ b/src/components/ContentGrid.astro @@ -1,6 +1,6 @@ --- + import CategoryBadge from './CategoryBadge.astro'; -import Author from '@components/AuthorDisplay.astro'; export interface Props { items: any[]; @@ -11,7 +11,6 @@ export interface Props { hasNextPage: boolean; endCursor: string | null; }; - perLoad?: number; // Больше не используется, но оставляем для обратной совместимости gridColumns?: 3 | 4; } @@ -21,11 +20,10 @@ const { type = 'latest', slug = '', pageInfo = { hasNextPage: false, endCursor: null }, - perLoad = 11, // Игнорируется gridColumns = 4 } = Astro.props; -// Конфиг без perLoad, так как будем вычислять на клиенте +// Конфиг для клиентского скрипта const loadMoreConfig = { type, slug, @@ -59,9 +57,9 @@ function shouldBeLarge(index: number, columns: number): boolean {
- {showCount && items.length > 0 && ( -

({items.length})

- )} + {showCount && items.length > 0 && ( +

Все статьи ({items.length})

+ )}
)} -
- - \ No newline at end of file + \ No newline at end of file diff --git a/src/components/EmbeddedPost.astro b/src/components/EmbeddedPost.astro new file mode 100644 index 0000000..9ee4e1d --- /dev/null +++ b/src/components/EmbeddedPost.astro @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/src/components/NewsSingle.astro b/src/components/NewsSingle.astro index 69d93b3..122097c 100644 --- a/src/components/NewsSingle.astro +++ b/src/components/NewsSingle.astro @@ -3,6 +3,9 @@ import { stripHtml } from '@/utils/htmlhelpers'; import Author from '@components/AuthorDisplay.astro'; import Subscribe from '@components/SubscribePost.astro'; import ShareButtons from '@components/ShareButtons.astro'; +import EmbeddedPost from '@components/EmbeddedPost.astro'; // шаблоны ссылок на статьи + + interface Props { @@ -56,14 +59,15 @@ const { post, pageInfo } = Astro.props; )} )} - - {post.content &&
} + {post.content &&
} + +
) : (
Новость не найдена
diff --git a/src/layouts/ContentLayout.astro b/src/layouts/ContentLayout.astro index c228da6..40c56e5 100644 --- a/src/layouts/ContentLayout.astro +++ b/src/layouts/ContentLayout.astro @@ -18,6 +18,7 @@ import '../styles/global.css'; {`${title}`} - Деловой журнал Профиль + @@ -44,7 +45,6 @@ import '../styles/global.css'; { text: "Архив", url: "/archive" }, ]} /> - diff --git a/src/layouts/MainLayout.astro b/src/layouts/MainLayout.astro index 3303f16..10dc749 100644 --- a/src/layouts/MainLayout.astro +++ b/src/layouts/MainLayout.astro @@ -18,6 +18,7 @@ import '../styles/global.css'; {`${title}`} - Деловой журнал Профиль + diff --git a/src/lib/api/posts.ts b/src/lib/api/posts.ts index a36334c..55548a5 100644 --- a/src/lib/api/posts.ts +++ b/src/lib/api/posts.ts @@ -345,7 +345,7 @@ export async function getPostsByTag(slug, first = 14, after = null) { } `; - console.log('Fetching with:', { first, after, slug }); // Добавим лог параметров + //console.log('Fetching with:', { first, after, slug }); // Добавим лог параметров const data = await fetchGraphQL(query, { first, after, slug }); @@ -616,6 +616,65 @@ export async function getTagBySlug(slug) { +// lib/graphql.js или src/lib/graphql.js + +/** подключаем пост по slug */ +export async function getPostBySlug(slug) { + const cacheKey = `post:${slug}`; + + return await cache.wrap( + cacheKey, + async () => { + const query = ` + query GetPostBySlug($slug: String!) { + profileArticleBy(slug: $slug) { + title + link + featuredImage { + node { + sourceUrl(size: LARGE) + altText + } + } + } + + aNewBy(slug: $slug) { + title + link + featuredImage { + node { + sourceUrl(size: LARGE) + altText + } + } + } + } + `; + + const data = await fetchGraphQL(query, { slug }); + console.log('Raw GraphQL response:', JSON.stringify(data, null, 2)); + + // Находим первый существующий пост + const post = data.profileArticleBy || data.aNewBy; + + if (!post) { + console.log('No post found for slug:', slug); + return null; + } + + return { + title: post.title, + url: post.link, + image: post.featuredImage?.node?.sourceUrl || null, + imageAlt: post.featuredImage?.node?.altText || post.title || '', + type: data.profileArticleBy ? 'profile_article' : 'anew' + }; + }, + { ttl: CACHE_TTL.POST_DETAIL } + ); +} + + /** * Получить тег с постами по slug с пагинацией */ diff --git a/src/pages/api/embedded-post.js b/src/pages/api/embedded-post.js new file mode 100644 index 0000000..8331608 --- /dev/null +++ b/src/pages/api/embedded-post.js @@ -0,0 +1,50 @@ +// src/pages/api/embedded-post.js + +import { getPostBySlug } from '@lib/api/posts'; + +export const prerender = false; + +export async function GET({ request }) { + // Получаем параметр url - это и есть slug + const url = new URL(request.url); + const slug = url.searchParams.get('url'); + + // Логируем для отладки + console.log('Received slug:', slug); + + if (!slug) { + return new Response(JSON.stringify({ error: 'Slug is required' }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } + + try { + // Используем slug напрямую, без извлечения + const postData = await getPostBySlug(slug); + + if (!postData) { + return new Response(JSON.stringify({ error: 'Post not found:' + slug }), { + status: 404, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Формируем полный URL для ссылки + postData.originalUrl = `https://profile.ru/${slug}`; + + return new Response(JSON.stringify(postData), { + status: 200, + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'public, max-age=3600' + } + }); + } catch (error) { + console.error('Error in embedded-post API:', error); + return new Response(JSON.stringify({ error: 'Internal server error' }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +} \ No newline at end of file diff --git a/src/scripts/embedded-content.js b/src/scripts/embedded-content.js new file mode 100644 index 0000000..3dbda2c --- /dev/null +++ b/src/scripts/embedded-content.js @@ -0,0 +1,178 @@ +(function() { + // Предотвращаем множественное выполнение + if (window._embeddedContentReplacerInitialized) return; + window._embeddedContentReplacerInitialized = true; + + const processedUrls = new Set(); + + // Главная функция инициализации + function initEmbeddedReplacer() { + // Проверяем наличие шаблонов + if (!document.getElementById('embedded-loading-template') || + !document.getElementById('embedded-card-template')) { + console.error('Embedded content templates not found'); + return; + } + + processBlockquotes(); + observeMutations(); + } + + // Поиск и обработка всех blockquote + function processBlockquotes() { + const blockquotes = document.querySelectorAll('blockquote.wp-embedded-content'); + blockquotes.forEach(processBlockquote); + } + + // Наблюдение за новыми элементами + function observeMutations() { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { + const blockquotes = node.querySelectorAll + ? node.querySelectorAll('blockquote.wp-embedded-content') + : []; + blockquotes.forEach(processBlockquote); + } + }); + }); + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + + // Клонирование шаблона + function cloneTemplate(templateId) { + const template = document.getElementById(templateId); + return template.content.cloneNode(true); + } + + // Функция для очистки slug от ID в конце + function cleanSlug(slug) { + if (!slug) return slug; + + const pattern = /-\d+$/; + + if (pattern.test(slug)) { + const cleanedSlug = slug.replace(pattern, ''); + console.log('Cleaned slug:', slug, '->', cleanedSlug); + return cleanedSlug; + } + + return slug; + } + + // Обработка одного blockquote + async function processBlockquote(blockquote) { + const link = blockquote.querySelector('a'); + if (!link) return; + + const href = link.getAttribute('href'); + + if (!href || !href.includes('profile.ru') || processedUrls.has(href)) { + return; + } + + processedUrls.add(href); + + // Сохраняем оригинальное содержимое + const originalContent = blockquote.innerHTML; + + // Показываем загрузку из шаблона + const loadingContent = cloneTemplate('embedded-loading-template'); + blockquote.innerHTML = ''; + blockquote.appendChild(loadingContent); + + try { + const urlObj = new URL(href); + const pathParts = urlObj.pathname.split('/').filter(part => part); + let slug = pathParts[pathParts.length - 1]; + + console.log('Original slug:', slug); + slug = cleanSlug(slug); + console.log('Processed slug:', slug); + + const postData = await fetchPostData(slug); + + if (postData && postData.title) { + replaceWithCard(blockquote, postData); + } else { + // Если данных нет - восстанавливаем оригинал + blockquote.innerHTML = originalContent; + } + } catch (error) { + console.error('Error processing embedded content:', error); + // В случае ошибки восстанавливаем оригинал + blockquote.innerHTML = originalContent; + } + } + + // Функция запроса к API + async function fetchPostData(slug) { + if (!slug) { + throw new Error('Slug is required'); + } + + const encodedSlug = encodeURIComponent(slug); + const apiUrl = `/api/embedded-post?url=${encodedSlug}`; + + console.log('Fetching from API:', apiUrl); + + const response = await fetch(apiUrl); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to fetch post data'); + } + + return data; + } + + // Замена на карточку из шаблона + function replaceWithCard(blockquote, post) { + // Клонируем шаблон карточки + const cardContent = cloneTemplate('embedded-card-template'); + + // Заполняем данными + const link = cardContent.querySelector('a'); + const imageContainer = cardContent.querySelector('.embedded-post-image-container'); + const image = cardContent.querySelector('.embedded-post-image'); + const title = cardContent.querySelector('.embedded-post-title'); + + if (link) { + link.href = post.originalUrl || post.url; + } + + if (post.image && image && imageContainer) { + image.src = post.image; + image.alt = post.imageAlt || post.title; + imageContainer.style.display = ''; + + image.onerror = function() { + this.style.display = 'none'; + imageContainer.style.display = 'none'; + }; + } else if (imageContainer) { + imageContainer.style.display = 'none'; + } + + if (title) { + title.textContent = post.title; + } + + // Очищаем blockquote и вставляем карточку + blockquote.innerHTML = ''; + blockquote.appendChild(cardContent); + } + + // Запускаем после загрузки DOM + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initEmbeddedReplacer); + } else { + initEmbeddedReplacer(); + } +})(); \ No newline at end of file diff --git a/src/styles/article.css b/src/styles/article.css index 624db01..9b02de2 100644 --- a/src/styles/article.css +++ b/src/styles/article.css @@ -20,7 +20,6 @@ font-size: 1.5rem; font-weight: bold; line-height: 1.5; - margin-bottom: 1rem; } .news-single :global(p a) { @@ -45,7 +44,7 @@ flex-shrink: 0; } -.publication__author :global(a){ +.publication__author a{ text-decoration: underline; } @@ -131,8 +130,18 @@ line-height: 1.6; } + .news-single li a{ color: #0d6efd; text-decoration: underline; } +.clearfix a{ + color: #0d6efd; +} + + +ul.contents{ + margin-bottom: 12px; +} + diff --git a/src/styles/global.css b/src/styles/global.css index 1a9dbf4..d96dfcd 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,6 +1,7 @@ @import './reset.css'; @import './ContentLayout.css'; @import './article.css'; +@import './embedded-content.css'; @import './components/ContentGrid.css'; @import './components/theme-colors.css';