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_TTL } from '@lib/cache/cache-ttl';
|
||||
|
||||
export async function getPostsByCoauthorName(coauthorName, first = 14, after = null) {
|
||||
// Создаем уникальный ключ для кэша
|
||||
const cacheKey = `coauthor-posts-known:${coauthorName}:${first}:${after || 'first-page'}`;
|
||||
// lib/api/authors.js
|
||||
export async function getPostsByCoauthorLogin(coauthorLogin, perLoad = 14, after = null) {
|
||||
const cacheKey = `coauthor-posts:${coauthorLogin}:${perLoad}:${after || 'first-page'}`;
|
||||
|
||||
return await cache.wrap(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const query = `
|
||||
query GetPostsByCoauthorName($first: Int!, $after: String, $coauthorName: String!) {
|
||||
# Информация об авторе
|
||||
users(where: {search: $coauthorName}) {
|
||||
nodes {
|
||||
databaseId
|
||||
name
|
||||
nicename
|
||||
email
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
description
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
const baseUrl = import.meta.env.WP_REST_BASE_URL?.replace(/\/$/, '');
|
||||
|
||||
// Формируем URL
|
||||
const url = `${baseUrl}/author/${coauthorLogin}/posts?per_page=${perLoad}${
|
||||
after ? `&cursor=${encodeURIComponent(after)}` : ''
|
||||
}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
return { posts: [], pageInfo: { hasNextPage: false, endCursor: null } };
|
||||
}
|
||||
throw new Error(`HTTP error: ${response.status}`);
|
||||
}
|
||||
`;
|
||||
|
||||
const data = await fetchGraphQL(query, {
|
||||
first,
|
||||
after,
|
||||
coauthorName
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
// Находим автора (первый результат поиска)
|
||||
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 {
|
||||
author: null,
|
||||
posts: [],
|
||||
pageInfo: { hasNextPage: false, endCursor: null },
|
||||
totalPosts: 0
|
||||
posts,
|
||||
pageInfo: {
|
||||
hasNextPage: data.pagination?.has_next || false,
|
||||
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'}`;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const { slug } = Astro.params;
|
||||
const author = await getAuthorData(slug);
|
||||
|
||||
const data = await getPostsByCoauthorLogin(slug);
|
||||
console.log(data);
|
||||
const posts = data.posts;
|
||||
|
||||
// Если автор не найден - 404
|
||||
@@ -27,6 +28,7 @@ const posts = data.posts;
|
||||
{author && (
|
||||
<div class="author-card">
|
||||
{author.avatar && (
|
||||
<div class="avatar">
|
||||
<img
|
||||
src={author.avatar}
|
||||
alt={author.name}
|
||||
@@ -34,33 +36,34 @@ const posts = data.posts;
|
||||
height="192"
|
||||
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 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>
|
||||
)}
|
||||
|
||||
@@ -70,8 +73,83 @@ const posts = data.posts;
|
||||
slug={slug}
|
||||
showCount={false}
|
||||
type='author'
|
||||
perLoad={11}
|
||||
perLoad={5}
|
||||
/>
|
||||
</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