diff --git a/src/components/ArchivePagination.astro b/src/components/ArchivePagination.astro new file mode 100644 index 0000000..080bf77 --- /dev/null +++ b/src/components/ArchivePagination.astro @@ -0,0 +1,216 @@ +--- + + +interface Props { + baseUrl: string; + page: number; + hasNextPage: boolean; + window?: number; // сколько страниц вокруг текущей +} + +const { + baseUrl, + page, + hasNextPage, + window = 2, +} = Astro.props; + +const pageUrl = (p: number) => + p === 1 ? baseUrl : `${baseUrl}/page/${p}`; + +// рассчитываем диапазон +const start = Math.max(1, page - window); +const end = page + window; +--- + + + + diff --git a/src/components/Footer.astro b/src/components/Footer.astro index c5f1e36..ee8d1b5 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -1,4 +1,8 @@ --- + +import FooterMenu from '@components/FooterMenu.astro'; + + interface Props { publicationName: string; organization: string; @@ -90,6 +94,8 @@ const footerId = `footer-profile`;
  • Правила применения рекомендательных технологий
  • + + diff --git a/src/components/Header/Header.astro b/src/components/Header/Header.astro index cbf1533..a3e2372 100644 --- a/src/components/Header/Header.astro +++ b/src/components/Header/Header.astro @@ -1,6 +1,7 @@ --- import Stores from './LazyStores.astro'; + import MainMenu from '@components/MainMenu.astro'; const MENU_ID = 103245; let menuItems = []; @@ -14,6 +15,8 @@ + + diff --git a/src/lib/api/menu.ts b/src/lib/api/menu.ts index 1c09103..b3e5c7b 100644 --- a/src/lib/api/menu.ts +++ b/src/lib/api/menu.ts @@ -1,71 +1,308 @@ +// lib/api/menu-api.ts + import { fetchGraphQL } from './graphql-client.js'; - -interface MenuItemNode { - uri: string; - url: string; - order: number; - label: string; +export interface MenuItem { + id: string; + databaseId: number; + uri: string; + url: string; + order: number; + label: string; + parentId: string | null; + target: string; + cssClasses: string[]; + description: string; + childItems?: { + nodes: MenuItem[]; + }; } -interface MenuNode { - name: string; - menuItems: { - nodes: MenuItemNode[]; - }; +export interface Menu { + id: string; + databaseId: number; + name: string; + slug: string; + locations: string[]; + menuItems: { + nodes: MenuItem[]; + }; } -interface MenusResponse { - menus: { - nodes: MenuNode[]; - }; +export type MenuIdentifier = + | { id: number } // По ID меню + | { location: string } // По локации + | { slug: string } // По слагу + | { name: string }; // По имени + +/** + * Получить меню по идентификатору + */ +export async function fetchMenu(identifier: MenuIdentifier): Promise { + try { + // Определяем тип запроса на основе переданного идентификатора + if ('id' in identifier) { + return await fetchMenuById(identifier.id); + } + if ('location' in identifier) { + return await fetchMenuByLocation(identifier.location); + } + if ('slug' in identifier) { + return await fetchMenuBySlug(identifier.slug); + } + if ('name' in identifier) { + return await fetchMenuByName(identifier.name); + } + + return null; + } catch (error) { + console.error('Error fetching menu:', error); + return null; + } } /** - * Get navigation menu from WordPress + * Получить меню по ID (самый надежный способ) */ -export async function navQuery(): Promise { - try { - const query = `{ - menus(where: {location: PRIMARY}) { - nodes { - name - menuItems { - nodes { - uri - url - order - label +async function fetchMenuById(id: number): Promise { + const query = ` + query GetMenuById($id: ID!) { + menu(id: $id, idType: DATABASE_ID) { + id + databaseId + name + slug + locations + menuItems(first: 100) { + nodes { + id + databaseId + uri + url + order + label + parentId + target + cssClasses + description + childItems(first: 50) { + nodes { + id + databaseId + label + uri + url + order + } + } + } + } } - } } - } - }`; + `; - return await executeQuery(query, {}, "navigation"); - } catch (error) { - log.error("Error fetching nav: " + error); - // Return fallback data for development - return { - menus: { - nodes: [ - { - name: "Primary", - menuItems: { - nodes: [ - { uri: "/", url: "/", order: 1, label: "Home" }, - { uri: "/about/", url: "/about/", order: 2, label: "About" }, - { - uri: "/contact/", - url: "/contact/", - order: 3, - label: "Contact", - }, - ], - }, - }, - ], - }, - }; - } + const variables = { id }; + const data = await fetchGraphQL(query, variables); + + if (data?.menu) { + return { + ...data.menu, + menuItems: data.menu.menuItems || { nodes: [] } + }; + } + + return null; +} + +/** + * Получить меню по локации + */ +async function fetchMenuByLocation(location: string): Promise { + const query = ` + query GetMenuByLocation($location: MenuLocationEnum!) { + menus(where: { location: $location }, first: 1) { + nodes { + id + databaseId + name + slug + locations + menuItems(first: 100) { + nodes { + id + databaseId + uri + url + order + label + parentId + target + cssClasses + description + childItems(first: 50) { + nodes { + id + databaseId + label + uri + url + order + } + } + } + } + } + } + } + `; + + const variables = { location }; + const data = await fetchGraphQL(query, variables); + return data?.menus?.nodes?.[0] || null; +} + +/** + * Получить меню по слагу + */ +async function fetchMenuBySlug(slug: string): Promise { + const query = ` + query GetMenuBySlug($slug: String!) { + menus(where: { slug: $slug }, first: 1) { + nodes { + id + databaseId + name + slug + locations + menuItems(first: 100) { + nodes { + id + databaseId + uri + url + order + label + parentId + target + cssClasses + description + } + } + } + } + } + `; + + const variables = { slug }; + const data = await fetchGraphQL(query, variables); + return data?.menus?.nodes?.[0] || null; +} + +/** + * Получить меню по имени + */ +async function fetchMenuByName(name: string): Promise { + const query = ` + query GetMenuByName($name: String!) { + menus(where: { name: $name }, first: 1) { + nodes { + id + databaseId + name + slug + locations + menuItems(first: 100) { + nodes { + id + databaseId + uri + url + order + label + parentId + target + cssClasses + description + } + } + } + } + } + `; + + const variables = { name }; + const data = await fetchGraphQL(query, variables); + return data?.menus?.nodes?.[0] || null; +} + +/** + * Преобразовать плоский список элементов меню в иерархическую структуру + */ +export function buildMenuHierarchy(menuItems: MenuItem[]): MenuItem[] { + const itemsMap = new Map(); + const rootItems: MenuItem[] = []; + + // Создаем map всех элементов + menuItems.forEach(item => { + itemsMap.set(item.id, { + ...item, + childItems: { nodes: [] } + }); + }); + + // Строим иерархию + menuItems.forEach(item => { + const menuItem = itemsMap.get(item.id)!; + + if (item.parentId && itemsMap.has(item.parentId)) { + const parent = itemsMap.get(item.parentId)!; + if (!parent.childItems) { + parent.childItems = { nodes: [] }; + } + parent.childItems.nodes.push(menuItem); + } else { + rootItems.push(menuItem); + } + }); + + // Сортируем элементы по order + const sortByOrder = (items: MenuItem[]) => + items.sort((a, b) => a.order - b.order); + + // Рекурсивно сортируем все уровни + function sortRecursive(items: MenuItem[]) { + sortByOrder(items); + items.forEach(item => { + if (item.childItems?.nodes.length) { + sortRecursive(item.childItems.nodes); + } + }); + } + + sortRecursive(rootItems); + return rootItems; +} + +/** + * Получить меню в виде иерархической структуры + */ +export async function getHierarchicalMenu(identifier: MenuIdentifier): Promise { + const menu = await fetchMenu(identifier); + if (!menu || !menu.menuItems?.nodes?.length) { + return []; + } + + return buildMenuHierarchy(menu.menuItems.nodes); +} + +/** + * Получить меню в виде плоского списка + */ +export async function getFlatMenu(identifier: MenuIdentifier): Promise { + const menu = await fetchMenu(identifier); + if (!menu || !menu.menuItems?.nodes?.length) { + return []; + } + + return menu.menuItems.nodes.sort((a, b) => a.order - b.order); } \ No newline at end of file diff --git a/src/lib/graphql-client.js b/src/lib/graphql-client.js index 8dc2fb3..acc638c 100644 --- a/src/lib/graphql-client.js +++ b/src/lib/graphql-client.js @@ -13,6 +13,9 @@ export async function fetchGraphQL(query, variables = {}) { const json = await res.json(); + console.log("Query:\n", query); + console.log("Variables:\n", JSON.stringify(variables, null, 2)); + if (json.errors) { // Выводим полный запрос и переменные для IDE diff --git a/src/pages/[...slug].astro b/src/pages/[...slug].astro index cbfd333..638bab7 100644 --- a/src/pages/[...slug].astro +++ b/src/pages/[...slug].astro @@ -1,26 +1,29 @@ --- -import { fetchCategory } from '@api/categories'; - import MainLayout from '@layouts/MainLayout.astro'; -import CategoryArchive from '@templates/CategoryArchive.astro'; -import { getCategory } from '@api/categories'; -import { getArchivePostsById } from '@api/archiveById'; +import { getNodeByURI, getCategoryPosts } from '@lib/api/all'; -export const prerender = false; // ISR -//export const revalidate = 60; +export const prerender = false; const { slug } = Astro.params; -const pathArray = Array.isArray(slug) ? slug : [slug]; +const uri = slug ? `/${slug}` : '/'; +let response; +let node = null; -// Получаем категорию по цепочке slug -const category = await getCategory(pathArray); -if (!category) return Astro.redirect('/404'); - -const perPage = 20; +try { + response = await getNodeByURI(uri); + node = response?.nodeByUri; +} catch (error) { + console.error('Error fetching node:', error); +} +// ISR кэширование +//Astro.response.headers.set( +// 'Cache-Control', +// 'public, s-maxage=3600, stale-while-revalidate=86400' +//); --- - -

    {category.name}

    -
    +Article +

    {uri}

    + diff --git a/src/pages/[...slug]/page/[page].astro b/src/pages/[...slug]/page/[page].astro deleted file mode 100644 index e69de29..0000000 diff --git a/src/pages/news/[...slug].astro b/src/pages/news/[...slug].astro new file mode 100644 index 0000000..5339291 --- /dev/null +++ b/src/pages/news/[...slug].astro @@ -0,0 +1,30 @@ +--- +import MainLayout from '@layouts/MainLayout.astro'; +import { getNodeByURI, getCategoryPosts } from '@lib/api/all'; + +export const prerender = false; + +const { slug } = Astro.params; +const uri = slug ? `/${slug}` : '/'; + +let response; +let node = null; + +try { + response = await getNodeByURI(uri); + node = response?.nodeByUri; +} catch (error) { + console.error('Error fetching node:', error); +} + +// ISR кэширование +//Astro.response.headers.set( +// 'Cache-Control', +// 'public, s-maxage=3600, stale-while-revalidate=86400' +//); +--- + +News + +

    {uri}

    + diff --git a/src/styles/global.css b/src/styles/global.css index 18fbeb6..cd58b4a 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -14,6 +14,7 @@ body { .container{ margin: 0 auto; width: 1200px; + padding-bottom: 60px; /* Подберите высоту под ваш свернутый футер */ } .header-info{