add shared buttons in post

This commit is contained in:
Profile Profile
2026-02-18 01:02:01 +03:00
parent abcc214ef6
commit 9e32f4f064
11 changed files with 687 additions and 276 deletions

View File

@@ -3,45 +3,57 @@ import { getLatestColonPost } from '@lib/api/colon-posts';
import Author from '@components/AuthorDisplay.astro'; import Author from '@components/AuthorDisplay.astro';
const colonPost = await getLatestColonPost(); const colonPost = await getLatestColonPost();
--- ---
{colonPost && ( {colonPost && (
<div class="colon-post-card"> <div class="colon-post-card">
<div class="colon-post-content"> <div class="split-flex">
<div class="colon-post-left"> <!-- ЛЕВЫЙ БЛОК: 30% ширины, фото впритык -->
{colonPost.featuredImage?.node?.sourceUrl && ( <div class="left-photo">
<a href={colonPost.uri}> {colonPost.featuredImage?.node?.sourceUrl ? (
<a href={colonPost.uri} class="photo-link">
<img <img
src={colonPost.featuredImage.node.sourceUrl} src={colonPost.featuredImage.node.sourceUrl}
alt={colonPost.featuredImage.node.altText || colonPost.secondaryTitle || colonPost.title} alt={colonPost.featuredImage.node.altText || colonPost.secondaryTitle || colonPost.title}
loading="lazy" loading="lazy"
class="colon-post-image" class="photo-img"
/> />
</a> </a>
) : (
<div class="photo-placeholder"></div>
)} )}
<div class="colon-post-meta">
<span>{new Date(colonPost.date).toLocaleDateString('ru-RU')}</span>
<div><Author post={colonPost} separator=", " /></div>
</div>
</div> </div>
<div class="colon-post-right"> <!-- ПРАВЫЙ БЛОК: 70% ширины, заголовок сверху, дата|автор снизу -->
<a href={colonPost.uri}> <div class="right-content">
<h3>{colonPost.secondaryTitle || colonPost.title}</h3> <div class="content-wrapper">
<!-- Заголовок жирным сверху -->
<a href={colonPost.uri} class="title-link">
<h3 class="bold-title">{colonPost.secondaryTitle || colonPost.title}</h3>
</a> </a>
<!-- Мета-информация внизу: дата | автор -->
<div class="meta-line">
<span class="date">{new Date(colonPost.date).toLocaleDateString('ru-RU')}</span>
<span class="separator">|</span>
<span class="author"><Author post={colonPost} separator=", " /></span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
)} )}
<style> <style>
/* Основная карточка */
.colon-post-card { .colon-post-card {
background: white; background: #ececec;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease; transition: transform 0.3s ease, box-shadow 0.3s ease;
width: 100%;
max-width: 800px; /* опционально, для демо */
height: 200px; /* фиксированная высота карточки */
} }
.colon-post-card:hover { .colon-post-card:hover {
@@ -49,137 +61,131 @@ const colonPost = await getLatestColonPost();
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
} }
.colon-post-content { /* Flex-контейнер: две части */
.split-flex {
display: flex; display: flex;
gap: 20px; height: 100%;
padding: 20px;
align-items: flex-start;
}
.colon-post-left {
flex: 0 0 200px;
position: relative;
}
.colon-post-image-link {
display: block;
overflow: hidden;
border-radius: 6px;
margin-bottom: 10px;
}
.colon-post-image {
width: 100%; width: 100%;
height: 150px;
object-fit: cover;
transition: transform 0.3s ease;
} }
.colon-post-image:hover { /* ЛЕВЫЙ БЛОК: ровно 30% */
transform: scale(1.05); .left-photo {
} flex: 0 0 34%; /* ширина 30%, не растягивается */
height: 100%;
.colon-post-image-placeholder { background: #d4d4d4; /* фон, если нет фото */
display: flex;
}
.photo-link {
display: flex; display: flex;
align-items: center;
justify-content: center;
width: 100%; width: 100%;
height: 150px; height: 100%;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 6px;
margin-bottom: 10px;
text-decoration: none; text-decoration: none;
} }
.placeholder-text { .photo-img {
color: #666; width: 100%;
font-size: 14px; height: 100%;
object-fit: cover; /* заполняет контейнер, сохраняя пропорции и обрезаясь */
display: block;
transition: transform 0.3s ease;
} }
.colon-post-meta { .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; display: flex;
align-items: center; flex-direction: column;
font-size: 12px; }
color: #666;
/* Обёртка для контента, чтобы занять всю высоту и распределить пространство */
.content-wrapper {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
/* Заголовок жирным */
.bold-title {
font-size: 1.125rem;
font-weight: 700;
line-height: 1.4; line-height: 1.4;
color: #2c3e50;
margin: 0 0 8px 0;
transition: color 0.3s ease;
} }
.colon-post-date { .title-link {
white-space: nowrap; text-decoration: none;
color: inherit;
} }
.meta-separator { .title-link:hover .bold-title {
margin: 0 5px; color: #3498db;
color: #ccc;
} }
.colon-post-author { /* Мета-строка: прижимаем к низу */
white-space: nowrap; .meta-line {
font-size: 0.9rem;
color: #666;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
margin-top: auto; /* это прижимает мету к низу */
} }
.author-link { .separator {
color: #aaa;
font-weight: 300;
}
.author :global(a) {
color: #2c3e50; color: #2c3e50;
text-decoration: none; text-decoration: none;
font-weight: 500; font-weight: 500;
} }
.author-link:hover { .author :global(a:hover) {
color: #3498db; color: #3498db;
text-decoration: underline; text-decoration: underline;
} }
.colon-post-right {
flex: 1;
}
.colon-post-title-link {
text-decoration: none;
color: inherit;
display: block;
}
.colon-post-title {
margin: 0 0 10px 0;
font-size: 20px;
line-height: 1.4;
color: #2c3e50;
transition: color 0.3s ease;
}
.colon-post-title:hover {
color: #3498db;
}
.colon-post-secondary-title {
font-size: 16px;
color: #666;
line-height: 1.5;
font-weight: 400;
}
/* Адаптивность */ /* Адаптивность */
@media (max-width: 768px) { @media (max-width: 600px) {
.colon-post-content { .colon-post-card {
flex-direction: column; height: auto;
gap: 15px; min-height: 180px;
} }
.colon-post-left { .left-photo {
flex: 0 0 auto; flex: 0 0 30%;
width: 100%; aspect-ratio: 1 / 1; /* сохраняем квадрат на мобильных */
height: auto;
} }
.colon-post-image, .right-content {
.colon-post-image-placeholder { padding: 12px 16px;
height: 200px;
} }
.colon-post-title { .bold-title {
font-size: 18px; font-size: 1.1rem;
}
} }
.colon-post-secondary-title { /* Если нужно точное соответствие макету, можно добавить медиа-запросы под свои нужды */
font-size: 15px;
}
}
</style> </style>

View File

@@ -2,7 +2,6 @@
import { getLatestAnews } from '../lib/api/posts.js'; import { getLatestAnews } from '../lib/api/posts.js';
import { fetchWPRestGet } from "@/lib/api/wp-rest-get-client"; import { fetchWPRestGet } from "@/lib/api/wp-rest-get-client";
// Даты/время // Даты/время
function formatDate(dateString: string): string { function formatDate(dateString: string): string {
const date = new Date(dateString); const date = new Date(dateString);
@@ -51,118 +50,129 @@ const hasNews = newsPosts.length > 0;
const hasTop = topPosts.length > 0; const hasTop = topPosts.length > 0;
// По умолчанию открываем вкладку, где есть данные // По умолчанию открываем вкладку, где есть данные
const defaultTab: "news" | "top" = hasTop ? "top" : "news"; const defaultTab: "news" | "top" = hasNews ? "news" : "top";
--- ---
{(hasNews || hasTop) && ( {(hasNews || hasTop) && (
<div class="endnews-container"> <div class="endnews-container" id="endnews-container">
<div class="endnews-header"> {/* Табы на всю ширину, каждый по 50% - теперь div'ы */}
<h4 class="endnews-title">Новости</h4>
</div>
{/* Radio-кнопки ВНЕ .endnews-tabs для работы CSS-селекторов */}
<input
class="endnews-tab-input"
type="radio"
name="endnews-tab"
id="endnews-tab-news"
checked={defaultTab === "news"}
/>
<input
class="endnews-tab-input"
type="radio"
name="endnews-tab"
id="endnews-tab-top"
checked={defaultTab === "top"}
/>
{/* Только labels в блоке табов */}
<div class="endnews-tabs"> <div class="endnews-tabs">
<label class="endnews-tab-label" for="endnews-tab-news">Новости</label> <div class={`endnews-tab-label ${defaultTab === 'news' ? 'active' : ''}`} data-tab="news">Новости</div>
<label class="endnews-tab-label" for="endnews-tab-top">Топ10</label> <div class={`endnews-tab-label ${defaultTab === 'top' ? 'active' : ''}`} data-tab="top">Топ10</div>
</div> </div>
{/* Контент: две панели, показываем нужную через :checked */} {/* Контент: две панели */}
<div class="latestnews-list"> <div class="latestnews-list">
<section class="endnews-panel endnews-panel--news" aria-label="Новости"> <section class={`endnews-panel endnews-panel--news ${defaultTab === 'news' ? 'active' : ''}`} aria-label="Новости">
{Object.entries(groupedNews).map(([dateKey, datePosts]) => ( {Object.entries(groupedNews).map(([dateKey, datePosts]) => (
<div class="latestnews-date-group" key={dateKey}> <div class="latestnews-date-group" key={dateKey}>
<div class="latestnews-date">{formatDate(dateKey + 'T00:00:00')}</div> <div class="latestnews-date">{formatDate(dateKey + 'T00:00:00')}</div>
{/* Переделано в ul > li */}
<ul class="latestnews-items">
{datePosts.map((post) => ( {datePosts.map((post) => (
<article class="lastnews-item" key={post.uri}> <li class="lastnews-item" key={post.uri}>
<div class="lastnews-time">{formatTime(post.date)}</div> <div class="lastnews-time">{formatTime(post.date)}</div>
<div class="lastnews-content"> <div class="lastnews-content">
<a href={post.uri || '#'} class="endnews-link"> <a href={post.uri || '#'} class="endnews-link">
{post.title} {post.title}
</a> </a>
</div> </div>
</article> </li>
))} ))}
</ul>
</div> </div>
))} ))}
</section> </section>
<section class="endnews-panel endnews-panel--top" aria-label="Топ-10"> <section class={`endnews-panel endnews-panel--top ${defaultTab === 'top' ? 'active' : ''}`} aria-label="Топ-10">
{topPosts.length > 0 ? (
<ul class="latestnews-items top-items">
{topPosts.map((post, i) => ( {topPosts.map((post, i) => (
<article class="lastnews-item" key={(post.uri ?? post.link ?? post.title) + i}> <li class="lastnews-item top-item" key={(post.uri ?? post.link ?? post.title) + i}>
<div class="ltopnews-content"> <div class="lastnews-content">
<a href={post.uri ?? post.link ?? '#'} class="endnews-link"> <a href={post.uri ?? post.link ?? '#'} class="endnews-link">
{post.title} {post.title}
</a> </a>
</div> </div>
</article> </li>
))} ))}
</ul>
{!hasTop && <div class="endnews-empty">Топ пока пуст</div>} ) : (
<div class="endnews-empty">Топ пока пуст</div>
)}
</section> </section>
</div> </div>
</div> </div>
)} )}
<script>
document.addEventListener('DOMContentLoaded', () => {
const container = document.getElementById('endnews-container');
if (!container) return;
const tabs = container.querySelectorAll('.endnews-tab-label');
const panels = {
news: container.querySelector('.endnews-panel--news'),
top: container.querySelector('.endnews-panel--top')
};
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const tabName = tab.dataset.tab;
// Обновляем классы на табах
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Обновляем классы на панелях
Object.values(panels).forEach(panel => {
if (panel) panel.classList.remove('active');
});
if (panels[tabName]) panels[tabName].classList.add('active');
});
});
});
</script>
<style> <style>
.endnews-container { width:100%; } .endnews-container { width:100%; }
.endnews-header{ /* Tabs на всю ширину */
padding:12px 16px;
background:#B61D1D;
border-radius:6px 6px 0 0;
}
.endnews-title{ color:#fff; margin:0; font-size:1.1rem; font-weight:600; }
/* Скрываем radio-кнопки */
.endnews-tab-input{
position:absolute;
opacity:0;
pointer-events:none;
}
/* Tabs */
.endnews-tabs{ .endnews-tabs{
display:flex; display:flex;
gap:8px; width:100%;
padding:10px 12px; background: #ECECEC;
border:1px solid #ECECEC; border-radius: 6px 6px 0 0;
border-top:none; overflow: hidden;
background:#fff;
}
.endnews-tab-label{
cursor:pointer;
user-select:none;
font-size:0.85rem;
font-weight:700;
padding:8px 10px;
border-radius:6px;
background:#f5f5f5;
color:#505258;
transition: background 0.2s, color 0.2s;
} }
/* Активный таб - используем общий сиблинг-селектор */ .endnews-tab-label{
#endnews-tab-news:checked ~ .endnews-tabs label[for="endnews-tab-news"], flex: 1 1 0;
#endnews-tab-top:checked ~ .endnews-tabs label[for="endnews-tab-top"]{ width: 50%;
background:#B61D1D; min-width: 50%;
color:#fff; max-width: 50%;
cursor:pointer;
user-select:none;
font-size:0.95rem;
font-weight:400;
padding:12px 10px;
text-align: center;
background: #ECECEC;
color: #BFBFBF;
transition: background 0.2s, color 0.2s, font-weight 0.2s, border 0.2s;
box-sizing: border-box;
border: 1px solid transparent;
border-bottom: none;
}
/* Активный таб - черный жирный на белом фоне */
.endnews-tab-label.active{
background: #FFFFFF;
color: #000000;
font-weight: 700;
border: 1px solid #ECECEC;
border-bottom: none;
} }
/* list container */ /* list container */
@@ -171,17 +181,40 @@ const defaultTab: "news" | "top" = hasTop ? "top" : "news";
border-top:none; border-top:none;
border-radius:0 0 6px 6px; border-radius:0 0 6px 6px;
background:#fff; background:#fff;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: none;
-ms-overflow-style: none;
}
/* Скрываем скроллбар */
.latestnews-list::-webkit-scrollbar {
display: none;
} }
/* Панели скрыты по умолчанию */ /* Панели скрыты по умолчанию */
.endnews-panel{ display:none; } .endnews-panel{ display:none; }
/* Показываем нужную панель по выбранному radio */ /* Показываем активную панель */
#endnews-tab-news:checked ~ .latestnews-list .endnews-panel--news{ display:block; } .endnews-panel.active{ display:block; }
#endnews-tab-top:checked ~ .latestnews-list .endnews-panel--top { display:block; }
/* Существующий стиль списка */ /* Стили для списка */
.latestnews-date-group{ padding:12px 16px; border-bottom:1px solid #f0f0f0; } .latestnews-items {
list-style: none;
margin: 0;
padding: 0;
}
.latestnews-items li{
font-size: 0.8125rem;
line-height: 1.2;
font-weight: 500;
}
.latestnews-date-group{
padding:12px 16px;
border-bottom:1px solid #f0f0f0;
}
.latestnews-date-group:last-child{ border-bottom:none; } .latestnews-date-group:last-child{ border-bottom:none; }
.latestnews-date{ .latestnews-date{
@@ -212,6 +245,46 @@ const defaultTab: "news" | "top" = hasTop ? "top" : "news";
margin-top:1px; margin-top:1px;
} }
/* Стили для топ-новостей */
.top-items {
padding: 8px 0 0 0;
}
.top-item {
position: relative;
padding: 10px 0 10px 16px;
margin: 0;
border-bottom: 1px solid #f0f0f0;
gap: 0;
}
.top-item:last-child {
border-bottom: none;
}
.top-item::before {
content: '';
background: #b51d1d;
width: 3px;
height: 20px;
position: absolute;
top: 50%;
left: 0;
-webkit-transform: translate(0, -50%);
-ms-transform: translate(0,-50%);
transform: translate(0, -50%);
}
.top-item .lastnews-content {
margin-left: 0;
padding-right: 16px;
}
.lastnews-time-top{
min-width:2.5rem;
flex-shrink:0;
}
.lastnews-content{ .lastnews-content{
line-height:1.3; line-height:1.3;
font-weight:500; font-weight:500;
@@ -221,18 +294,6 @@ const defaultTab: "news" | "top" = hasTop ? "top" : "news";
flex:1; flex:1;
} }
.topnews-content{
line-height:1.3;
font-weight:500;
font-size:0.9em;
color:#000;
transition:color 0.2s;
margin-left: 4px;
}
.endnews-link{ color:inherit; text-decoration:none; display:block; } .endnews-link{ color:inherit; text-decoration:none; display:block; }
.endnews-link:hover{ color:#B61D1D; } .endnews-link:hover{ color:#B61D1D; }
@@ -244,14 +305,36 @@ const defaultTab: "news" | "top" = hasTop ? "top" : "news";
} }
.endnews-empty{ padding:12px 16px; color:#6b6d72; font-size:0.85rem; } .endnews-empty{ padding:12px 16px; color:#6b6d72; font-size:0.85rem; }
/* Десктоп скролл */ /* Десктоп версия */
@media (min-width:1024px){ @media (min-width:1024px){
.endnews-container{ height:500px; display:flex; flex-direction:column; } .endnews-container{ height:500px; display:flex; flex-direction:column; }
.latestnews-list{ flex:1; overflow-y:auto; overflow-x:hidden; } .latestnews-list{ flex:1; }
} }
/* Мобильная версия */
@media (max-width:1023px){ @media (max-width:1023px){
.endnews-container{ height:auto; } .endnews-container{ height:auto; }
.latestnews-list{ overflow:visible; } .latestnews-list{ overflow:visible; }
/* Скрываем вкладку Топ-10 на мобильных */
.endnews-tab-label[data-tab="top"] {
display: none;
}
/* Оставляем только вкладку Новости на всю ширину */
.endnews-tab-label[data-tab="news"] {
width: 100%;
max-width: 100%;
min-width: 100%;
}
/* На мобильных всегда показываем панель новостей */
.endnews-panel--top {
display: none !important;
}
.endnews-panel--news {
display: block !important;
}
} }
</style> </style>

View File

@@ -17,8 +17,24 @@ if (!menu) {
--- ---
<nav class="primary-nav" aria-label="Main navigation"> <nav class="primary-nav" aria-label="Main navigation">
<div class="primary-nav__wrapper">
<div class="primary-nav__content"> <div class="primary-nav__content">
<button class="primary-nav__burger" aria-label="Toggle menu"></button> <button class="primary-nav__burger" aria-label="Toggle menu"></button>
<!-- Логотип для фиксированного меню -->
<div class="primary-nav__logo-scroll">
<a href="/" aria-label="На главную">
<img
alt="Профиль"
width="120"
height="27"
src="https://cdn.profile.ru/wp-content/themes/profile/assets/img/profile-logo-delovoy.svg"
class="primary-nav__logo-image"
/>
</a>
</div>
<ul class="primary-nav__list"> <ul class="primary-nav__list">
{menu.menuItems.nodes.map(item => { {menu.menuItems.nodes.map(item => {
const colorClass = item.menuItemColor ? `color-${item.menuItemColor}` : 'color-black'; const colorClass = item.menuItemColor ? `color-${item.menuItemColor}` : 'color-black';
@@ -36,6 +52,7 @@ if (!menu) {
})} })}
</ul> </ul>
</div> </div>
</div>
</nav> </nav>
<BurgerMenu colorMenuId={103246} standardMenuId={103247} submenuId={3341} /> <BurgerMenu colorMenuId={103246} standardMenuId={103247} submenuId={3341} />
@@ -43,16 +60,88 @@ if (!menu) {
<style> <style>
.primary-nav { .primary-nav {
margin: 12px 0; margin: 12px 0;
position: relative;
border-top: 1px solid black; border-top: 1px solid black;
border-bottom: 1px solid black; border-bottom: 1px solid black;
background-color: white;
width: 100%;
transition: all 0.3s ease;
z-index: 1000;
}
/* Стили только для десктопа */
@media (min-width: 768px) {
.primary-nav.fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
margin: 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
/* Добавляем отступ для контента, когда меню фиксированное */
.primary-nav.fixed + * {
margin-top: 60px;
}
}
/* Обертка для центрирования контента */
.primary-nav__wrapper {
width: 100%;
background-color: white;
}
/* При фиксированном меню обертка тоже фиксируется */
@media (min-width: 768px) {
.primary-nav.fixed .primary-nav__wrapper {
max-width: 1200px;
margin: 0 auto;
width: 100%;
padding: 0 20px; /* Добавляем отступы по бокам */
box-sizing: border-box;
}
} }
.primary-nav__content { .primary-nav__content {
display: flex; display: flex;
align-items: stretch;
position: relative; position: relative;
min-height: 48px; min-height: 48px;
width: 100%;
margin: 0 auto;
}
/* Для десктопа добавляем ограничение ширины */
@media (min-width: 768px) {
.primary-nav__content {
max-width: 1200px;
}
}
/* Стили для логотипа, появляющегося при скролле */
.primary-nav__logo-scroll {
display: none;
opacity: 0;
transition: opacity 0.3s ease;
margin-right: 20px;
flex-shrink: 0;
}
/* Показываем логотип только когда меню фиксированное и на десктопе */
@media (min-width: 768px) {
.primary-nav.fixed .primary-nav__logo-scroll {
display: flex;
align-items: center;
opacity: 1;
margin-left: 12px;
}
}
.primary-nav__logo-image {
display: block;
width: auto;
height: 27px;
} }
.primary-nav__burger { .primary-nav__burger {
@@ -112,3 +201,89 @@ if (!menu) {
} }
} }
</style> </style>
<script>
(function() {
document.addEventListener('DOMContentLoaded', function() {
const primaryNav = document.querySelector('.primary-nav');
const header = document.querySelector('.header');
if (!primaryNav || !header) return;
// Проверяем, что это десктоп
let isDesktop = window.innerWidth >= 768;
// Убираем ранний return, чтобы эффект мог включаться/выключаться при ресайзе
// Создаем плейсхолдер для сохранения места при фиксированном меню
const placeholder = document.createElement('div');
placeholder.className = 'primary-nav-placeholder';
placeholder.style.display = 'none';
placeholder.style.height = primaryNav.offsetHeight + 'px';
placeholder.style.width = '100%';
placeholder.style.backgroundColor = 'transparent';
// Вставляем плейсхолдер после меню
if (primaryNav.parentNode) {
primaryNav.parentNode.insertBefore(placeholder, primaryNav.nextSibling);
}
function handleScroll() {
// Применяем эффект только на десктопе
if (!isDesktop) return;
const topBar = document.querySelector('.top-bar');
if (!topBar) return;
const topBarHeight = topBar.offsetHeight;
const scrollPosition = window.scrollY;
if (scrollPosition > topBarHeight) {
if (!primaryNav.classList.contains('fixed')) {
primaryNav.classList.add('fixed');
if (placeholder.parentNode) {
placeholder.style.display = 'block';
// Обновляем высоту плейсхолдера
placeholder.style.height = primaryNav.offsetHeight + 'px';
}
}
} else {
if (primaryNav.classList.contains('fixed')) {
primaryNav.classList.remove('fixed');
if (placeholder.parentNode) {
placeholder.style.display = 'none';
}
}
}
}
window.addEventListener('scroll', handleScroll);
// Вызываем сразу для правильного начального состояния
handleScroll();
// Обновляем при изменении размера окна
window.addEventListener('resize', function() {
const newIsDesktop = window.innerWidth >= 768;
if (newIsDesktop !== isDesktop) {
isDesktop = newIsDesktop;
if (!isDesktop) {
// Стали мобильным - убираем эффект
primaryNav.classList.remove('fixed');
if (placeholder.parentNode) {
placeholder.style.display = 'none';
}
} else {
// Стали десктопом - проверяем скролл
handleScroll();
}
}
if (primaryNav.classList.contains('fixed')) {
placeholder.style.height = primaryNav.offsetHeight + 'px';
}
});
});
})();
</script>

View File

@@ -136,7 +136,7 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', {
left: 16px; left: 16px;
padding: 6px 12px; padding: 6px 12px;
border-radius: 4px; border-radius: 4px;
font-size: 0.75rem; font-size: 0.625rem;
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
@@ -164,7 +164,7 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', {
.title-overlay { .title-overlay {
margin: 0 0 12px 0; margin: 0 0 12px 0;
font-size: 1.5rem; font-size: 1.4rem;
font-weight: 700; font-weight: 700;
line-height: 1.3; line-height: 1.3;
} }
@@ -182,7 +182,6 @@ const formattedDate = postDate.toLocaleDateString('ru-RU', {
.author-overlay { .author-overlay {
font-size: 0.875rem; font-size: 0.875rem;
opacity: 0.9; opacity: 0.9;
font-style: italic;
} }
/* Адаптивность */ /* Адаптивность */

View File

@@ -1,4 +1,8 @@
--- ---
import { stripHtml } from '@/utils/htmlhelpers';
import Author from '@components/AuthorDisplay.astro';
import Subscribe from '@components/SubscribePost.astro';
import ShareButtons from '@components/ShareButtons.astro';
interface Props { interface Props {
post: any; post: any;
@@ -6,18 +10,114 @@ interface Props {
} }
const { post, pageInfo } = Astro.props; const { post, pageInfo } = Astro.props;
--- ---
{post ? ( {post ? (
<div class="article-wrapper">
<article class="news-single"> <article class="news-single">
<h1>{post.title}</h1> <div class="article_info">
<div class="meta"> <div class="publication__data">{post.date && <time>{new Date(post.date).toLocaleDateString('ru-RU')}</time>}</div>
{post.date && <time>{new Date(post.date).toLocaleDateString('ru-RU')}</time>} <span class="author"><Author post={post} separator=", " /></span>
</div> </div>
<h1>{post.title}</h1>
{post.secondaryTitle && <p class="secondary-title">{post.secondaryTitle}</p>}
{post.featuredImage?.node?.sourceUrl && (
<figure class="featured-image">
<img
src={post.featuredImage.node.sourceUrl}
alt={post.featuredImage.node.altText || post.title}
width={post.featuredImage.node.mediaDetails?.width || 1200}
height={post.featuredImage.node.mediaDetails?.height || 675}
loading="eager"
class="post-image"
/>
{(post.featuredImage.node.description || post.featuredImage.node.caption) && (
<figcaption class="image-caption">
{post.featuredImage.node.description && (
<p>{stripHtml(post.featuredImage.node.description)}</p>
)}
{post.featuredImage.node.caption && (
<span>©{stripHtml(post.featuredImage.node.caption)}</span>
)}
</figcaption>
)}
</figure>
)}
{post.content && <div set:html={post.content} />} {post.content && <div set:html={post.content} />}
</article> </article>
<ShareButtons url={post.uri} title={post.title} />
<Subscribe />
</div>
) : ( ) : (
<div>Новость не найдена</div> <div>Новость не найдена</div>
)} )}
<style>
.article-wrapper {
position: relative;
max-width: 75%;
padding-bottom: 2rem;
}
.news-single h1{
font-size: 2.375rem;
line-height: 1.2;
margin: 0 0 0.625rem;
}
.secondary-title {
font-size: 1.25rem;
line-height: 1.4;
color: #666;
margin: 0.5rem 0 1rem;
font-weight: 400;
}
.article_info{
display: flex;
gap: 8px;
}
.publication__data::after{
content: '';
position: absolute;
left: 0;
top: 50%;
height: 12px;
margin-top: -6px;
border-left: 1px solid grey;
}
.featured-image {
margin: 1.5rem 0;
width: 100%;
}
.post-image {
width: 100%;
height: auto;
display: block;
border-radius: 8px;
object-fit: cover;
}
.image-caption {
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
text-align: left;
}
.image-caption p {
margin: 0 0 0.25rem 0;
}
.image-caption span {
font-style: italic;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,10 @@
---
---
<div class="clearfix">
<p>
Читайте на смартфоне наши Telegram-каналы: <a href="https://t.me/profile_newzzz/" target="_blank">Профиль-News</a>, и <a href="https://t.me/profilejournal" target="_blank">журнал Профиль</a>. Скачивайте полностью <a href="https://profile.ru/mobilnye-prilozheniya/" target="_blank">бесплатное мобильное</a> приложение журнала "Профиль".
</p>
</div>

View File

@@ -3,6 +3,7 @@
const { title, description } = Astro.props; const { title, description } = Astro.props;
import Header from '../components/Header/Header.astro'; import Header from '../components/Header/Header.astro';
import Header_lite from '../components/Header/Header_lite.astro';
import CurrentDate from '../components/Header/CurrentDate.astro'; import CurrentDate from '../components/Header/CurrentDate.astro';
import Footer from '../components/Footer.astro'; import Footer from '../components/Footer.astro';
@@ -34,7 +35,7 @@ import '../styles/global.css';
</div> </div>
<Footer <Footer
publicationName="ТехноВестник" publicationName="Профиль"
organization="Учредитель: ИДР. Все права защищены." organization="Учредитель: ИДР. Все права защищены."
menuItems={[ menuItems={[
{ text: "О нас", url: "/about" }, { text: "О нас", url: "/about" },
@@ -47,11 +48,3 @@ import '../styles/global.css';
</body> </body>
</html> </html>
<style>
main {
width:100%;
max-width: 1024px;
margin:auto;
}
</style>

View File

@@ -137,6 +137,7 @@ export async function getProfileArticleById(databaseId) {
id id
databaseId databaseId
title title
secondaryTitle
content content
excerpt excerpt
uri uri
@@ -150,6 +151,7 @@ export async function getProfileArticleById(databaseId) {
sourceUrl sourceUrl
altText altText
caption caption
description
mediaDetails { mediaDetails {
width width
height height
@@ -169,6 +171,15 @@ export async function getProfileArticleById(databaseId) {
uri uri
} }
} }
# Соавторы как массив
coauthors {
id
name
firstName
lastName
url
description
}
categories { categories {
nodes { nodes {
id id
@@ -195,7 +206,6 @@ export async function getProfileArticleById(databaseId) {
} }
/** /**
* Получить Anews пост по databaseId * Получить Anews пост по databaseId
*/ */

View File

@@ -40,6 +40,7 @@ if (pageInfo.type === 'single') { //одиночная статья
description="Информационное агентство Деловой журнал Профиль" description="Информационное агентство Деловой журнал Профиль"
> >
{pageInfo.type === 'single' && article ? ( {pageInfo.type === 'single' && article ? (
<NewsSingle post={article} pageInfo={pageInfo} /> <NewsSingle post={article} pageInfo={pageInfo} />
) : ( ) : (

View File

@@ -47,6 +47,10 @@ a {
font-weight: 700; font-weight: 700;
} }
main {
width:100%;
}
@media (max-width: 767px) { @media (max-width: 767px) {

30
src/utils/htmlhelpers.ts Normal file
View File

@@ -0,0 +1,30 @@
/**
* Удаляет HTML-теги и декодирует HTML-сущности
*/
export function stripHtml(html: string): string {
if (!html) return '';
return html
.replace(/<[^>]*>/g, '')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&#039;/g, "'")
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&nbsp;/g, ' ');
}
/**
* Только декодирует HTML-сущности, не удаляет теги
*/
export function decodeHtmlEntities(text: string): string {
if (!text) return '';
return text
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&#039;/g, "'")
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&nbsp;/g, ' ');
}