add style author page
This commit is contained in:
@@ -4,186 +4,96 @@ import { fetchGraphQL } from './graphql-client.js';
|
|||||||
import { cache } from '@lib/cache/manager.js';
|
import { cache } from '@lib/cache/manager.js';
|
||||||
import { CACHE_TTL } from '@lib/cache/cache-ttl';
|
import { CACHE_TTL } from '@lib/cache/cache-ttl';
|
||||||
|
|
||||||
export async function getPostsByCoauthorName(coauthorName, first = 14, after = null) {
|
// lib/api/authors.js
|
||||||
// Создаем уникальный ключ для кэша
|
export async function getPostsByCoauthorLogin(coauthorLogin, perLoad = 14, after = null) {
|
||||||
const cacheKey = `coauthor-posts-known:${coauthorName}:${first}:${after || 'first-page'}`;
|
const cacheKey = `coauthor-posts:${coauthorLogin}:${perLoad}:${after || 'first-page'}`;
|
||||||
|
|
||||||
return await cache.wrap(
|
return await cache.wrap(
|
||||||
cacheKey,
|
cacheKey,
|
||||||
async () => {
|
async () => {
|
||||||
const query = `
|
try {
|
||||||
query GetPostsByCoauthorName($first: Int!, $after: String, $coauthorName: String!) {
|
const baseUrl = import.meta.env.WP_REST_BASE_URL?.replace(/\/$/, '');
|
||||||
# Информация об авторе
|
|
||||||
users(where: {search: $coauthorName}) {
|
// Формируем URL
|
||||||
nodes {
|
const url = `${baseUrl}/author/${coauthorLogin}/posts?per_page=${perLoad}${
|
||||||
databaseId
|
after ? `&cursor=${encodeURIComponent(after)}` : ''
|
||||||
name
|
}`;
|
||||||
nicename
|
|
||||||
email
|
const response = await fetch(url);
|
||||||
avatar {
|
|
||||||
url
|
if (!response.ok) {
|
||||||
}
|
if (response.status === 404) {
|
||||||
description
|
return { posts: [], pageInfo: { hasNextPage: false, endCursor: null } };
|
||||||
posts {
|
|
||||||
pageInfo {
|
|
||||||
total
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Посты автора
|
|
||||||
contentNodes(
|
|
||||||
first: $first
|
|
||||||
after: $after
|
|
||||||
where: {
|
|
||||||
contentTypes: [PROFILE_ARTICLE, ANEW]
|
|
||||||
coauthorName: $coauthorName
|
|
||||||
orderby: { field: DATE, order: DESC }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
pageInfo {
|
|
||||||
hasNextPage
|
|
||||||
endCursor
|
|
||||||
}
|
|
||||||
nodes {
|
|
||||||
__typename
|
|
||||||
id
|
|
||||||
databaseId
|
|
||||||
uri
|
|
||||||
date
|
|
||||||
|
|
||||||
... on NodeWithTitle {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
|
|
||||||
... on NodeWithFeaturedImage {
|
|
||||||
featuredImage {
|
|
||||||
node {
|
|
||||||
sourceUrl(size: LARGE)
|
|
||||||
altText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Для кастомных типов
|
|
||||||
... on ProfileArticle {
|
|
||||||
categories {
|
|
||||||
nodes {
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
... on ANew {
|
|
||||||
categories {
|
|
||||||
nodes {
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
... on NodeWithAuthor {
|
|
||||||
author {
|
|
||||||
node {
|
|
||||||
name
|
|
||||||
avatar {
|
|
||||||
url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Coauthors для ProfileArticle
|
|
||||||
... on ProfileArticle {
|
|
||||||
coauthors {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
url
|
|
||||||
nicename
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Coauthors для ANew
|
|
||||||
... on ANew {
|
|
||||||
coauthors {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
url
|
|
||||||
nicename
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
throw new Error(`HTTP error: ${response.status}`);
|
||||||
}
|
}
|
||||||
`;
|
|
||||||
|
|
||||||
const data = await fetchGraphQL(query, {
|
const data = await response.json();
|
||||||
first,
|
|
||||||
after,
|
|
||||||
coauthorName
|
|
||||||
});
|
|
||||||
|
|
||||||
// Находим автора (первый результат поиска)
|
// Преобразуем данные для компонента
|
||||||
const author = data.users?.nodes?.[0];
|
const posts = (data.data || []).map(post => ({
|
||||||
|
// ID и ссылки
|
||||||
|
databaseId: post.id,
|
||||||
|
uri: post.link, // компонент использует uri
|
||||||
|
|
||||||
|
// Основные поля
|
||||||
|
title: post.title,
|
||||||
|
date: post.date,
|
||||||
|
|
||||||
|
// Картинка - приводим к формату GraphQL
|
||||||
|
featuredImage: post.featured_image?.medium ? {
|
||||||
|
node: {
|
||||||
|
sourceUrl: post.featured_image.medium,
|
||||||
|
altText: post.title
|
||||||
|
}
|
||||||
|
} : null,
|
||||||
|
|
||||||
|
// Категории - приводим к формату nodes
|
||||||
|
categories: {
|
||||||
|
nodes: (post.categories || []).map(cat => ({
|
||||||
|
name: cat.name,
|
||||||
|
slug: cat.slug,
|
||||||
|
color: cat.color || null
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
// Соавторы - компонент ожидает массив с name и nickname
|
||||||
|
coauthors: (post.coauthors || []).map(coauthor => ({
|
||||||
|
name: coauthor.name,
|
||||||
|
nickname: coauthor.id, // id используем как nickname для ссылки
|
||||||
|
node: {
|
||||||
|
name: coauthor.name,
|
||||||
|
nickname: coauthor.id
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
if (!author) {
|
|
||||||
return {
|
return {
|
||||||
author: null,
|
posts,
|
||||||
posts: [],
|
pageInfo: {
|
||||||
pageInfo: { hasNextPage: false, endCursor: null },
|
hasNextPage: data.pagination?.has_next || false,
|
||||||
totalPosts: 0
|
endCursor: data.pagination?.next_cursor || null
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error fetching author posts:`, error);
|
||||||
|
return { posts: [], pageInfo: { hasNextPage: false, endCursor: null } };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обрабатываем посты
|
|
||||||
const posts = data.contentNodes?.nodes?.map(node => {
|
|
||||||
// Приводим coauthors к единому формату
|
|
||||||
if (node.coauthors) {
|
|
||||||
node.coauthors = node.coauthors.map(coauthor => ({
|
|
||||||
id: coauthor.id,
|
|
||||||
nickname: coauthor.nickname,
|
|
||||||
name: coauthor.name || '',
|
|
||||||
firstName: coauthor.firstName || '',
|
|
||||||
lastName: coauthor.lastName || '',
|
|
||||||
url: coauthor.url || ''
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
author: {
|
|
||||||
id: author.databaseId,
|
|
||||||
name: author.name,
|
|
||||||
login: author.nicename,
|
|
||||||
email: author.email,
|
|
||||||
avatar: author.avatar?.url,
|
|
||||||
description: author.description,
|
|
||||||
totalPosts: author.posts?.pageInfo?.total || posts.length
|
|
||||||
},
|
|
||||||
posts,
|
|
||||||
pageInfo: data.contentNodes?.pageInfo || {
|
|
||||||
hasNextPage: false,
|
|
||||||
endCursor: null
|
|
||||||
},
|
|
||||||
authorName: coauthorName
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
{ ttl: CACHE_TTL.POSTS }
|
{ ttl: 3600 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// все посты автора
|
// все посты автора
|
||||||
export async function getPostsByCoauthorLogin(login, first = 14, after = null) {
|
export async function getPostsByCoauthorLoginQL(login, first = 14, after = null) {
|
||||||
// Создаем уникальный ключ для кэша
|
// Создаем уникальный ключ для кэша
|
||||||
const cacheKey = `coauthor-posts-by-login:${login}:${first}:${after || 'first-page'}`;
|
const cacheKey = `coauthor-posts-by-login:${login}:${first}:${after || 'first-page'}`;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const { slug } = Astro.params;
|
|||||||
const author = await getAuthorData(slug);
|
const author = await getAuthorData(slug);
|
||||||
|
|
||||||
const data = await getPostsByCoauthorLogin(slug);
|
const data = await getPostsByCoauthorLogin(slug);
|
||||||
|
console.log(data);
|
||||||
const posts = data.posts;
|
const posts = data.posts;
|
||||||
|
|
||||||
// Если автор не найден - 404
|
// Если автор не найден - 404
|
||||||
@@ -27,6 +28,7 @@ const posts = data.posts;
|
|||||||
{author && (
|
{author && (
|
||||||
<div class="author-card">
|
<div class="author-card">
|
||||||
{author.avatar && (
|
{author.avatar && (
|
||||||
|
<div class="avatar">
|
||||||
<img
|
<img
|
||||||
src={author.avatar}
|
src={author.avatar}
|
||||||
alt={author.name}
|
alt={author.name}
|
||||||
@@ -34,33 +36,34 @@ const posts = data.posts;
|
|||||||
height="192"
|
height="192"
|
||||||
class="avatar"
|
class="avatar"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<h1>{author.name}</h1>
|
|
||||||
|
|
||||||
{(author.firstName || author.lastName) && (
|
|
||||||
<p class="full-name">
|
|
||||||
{author.firstName} {author.lastName}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{author.bio && (
|
|
||||||
<div class="bio">{author.bio}</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{author.social && Object.values(author.social).some(Boolean) && (
|
|
||||||
<div class="social-links">
|
|
||||||
{author.social.twitter && (
|
|
||||||
<a href={author.social.twitter}>Twitter</a>
|
|
||||||
)}
|
|
||||||
{author.social.facebook && (
|
|
||||||
<a href={author.social.facebook}>Facebook</a>
|
|
||||||
)}
|
|
||||||
{author.social.instagram && (
|
|
||||||
<a href={author.social.instagram}>Instagram</a>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div class="author-info">
|
||||||
|
{(author.firstName || author.lastName) && (
|
||||||
|
<h1 class="full-name">
|
||||||
|
{author.firstName} {author.lastName}
|
||||||
|
</h1>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{author.bio && (
|
||||||
|
<div class="bio">{author.bio}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{author.social && Object.values(author.social).some(Boolean) && (
|
||||||
|
<div class="social-links">
|
||||||
|
{author.social.twitter && (
|
||||||
|
<a href={author.social.twitter}>Twitter</a>
|
||||||
|
)}
|
||||||
|
{author.social.facebook && (
|
||||||
|
<a href={author.social.facebook}>Facebook</a>
|
||||||
|
)}
|
||||||
|
{author.social.instagram && (
|
||||||
|
<a href={author.social.instagram}>Instagram</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -70,8 +73,83 @@ const posts = data.posts;
|
|||||||
slug={slug}
|
slug={slug}
|
||||||
showCount={false}
|
showCount={false}
|
||||||
type='author'
|
type='author'
|
||||||
perLoad={11}
|
perLoad={5}
|
||||||
/>
|
/>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.author-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch; /* Все блоки одной высоты */
|
||||||
|
min-height: 350px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-card .avatar {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
height:auto;
|
||||||
|
background: linear-gradient(to right, transparent 40%, #ececec 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-card .avatar IMG{
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 20px auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-card .author-info {
|
||||||
|
display:flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
background-color: #ececec;
|
||||||
|
padding: 0 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info h1{
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 769px) {
|
||||||
|
.author-card {
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: auto; /* Убираем минимальную высоту */
|
||||||
|
background-color: transparent; /* Убираем фон, перенесем на блоки */
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-card .avatar {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: auto; /* Убираем фиксированное соотношение */
|
||||||
|
height: auto;
|
||||||
|
background: #ececec; /* Простой фон вместо градиента */
|
||||||
|
padding: 30px 0; /* Отступы сверху/снизу */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-card .avatar IMG {
|
||||||
|
width: min(300px, 80%); /* Не больше 192px, но может быть меньше */
|
||||||
|
aspect-ratio: 1/1; /* Соотношение сторон 1:1 (квадрат) */
|
||||||
|
object-fit: cover;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-card .author-info {
|
||||||
|
flex: none; /* Убираем flex: 1 */
|
||||||
|
width: 100%;
|
||||||
|
padding: 30px 20px; /* Уменьшаем боковые отступы */
|
||||||
|
background-color: #ececec;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user