ваше сообщение коммита
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -213,3 +213,9 @@ sync-to-vds.sh
|
||||
|
||||
# Database initialization helper script
|
||||
scripts/internal/db/db_init_helper.sh
|
||||
|
||||
# Blog content (будет перенесен в блог приложения)
|
||||
blog-content/
|
||||
|
||||
# Public docs (загружены в БД)
|
||||
public-docs/
|
||||
|
||||
@@ -1590,6 +1590,107 @@ router.get('/blog/:slug', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Получить публичную страницу по slug (для /content/published)
|
||||
router.get('/published/:slug', async (req, res) => {
|
||||
try {
|
||||
const tableName = `admin_pages_simple`;
|
||||
let slug = req.params.slug;
|
||||
|
||||
// Декодируем slug (на случай если он был закодирован)
|
||||
try {
|
||||
slug = decodeURIComponent(slug);
|
||||
} catch (e) {
|
||||
console.warn('[pages] Ошибка декодирования slug:', e.message);
|
||||
}
|
||||
|
||||
// Валидация slug
|
||||
if (!slug || typeof slug !== 'string' || slug.trim() === '') {
|
||||
return res.status(400).json({ error: 'Невалидный slug' });
|
||||
}
|
||||
|
||||
slug = slug.trim();
|
||||
|
||||
// Проверяем, есть ли таблица
|
||||
const existsRes = await db.getQuery()(
|
||||
`SELECT to_regclass($1) as exists`, [tableName]
|
||||
);
|
||||
|
||||
if (!existsRes.rows[0].exists) {
|
||||
return res.status(404).json({ error: 'Страница не найдена' });
|
||||
}
|
||||
|
||||
// Получаем страницу по slug (без условия show_in_blog)
|
||||
const { rows } = await db.getQuery()(
|
||||
`SELECT * FROM ${tableName}
|
||||
WHERE slug = $1
|
||||
AND visibility = 'public'
|
||||
AND status = 'published'
|
||||
LIMIT 1`,
|
||||
[slug]
|
||||
);
|
||||
|
||||
console.log(`[pages] GET /published/:slug: поиск по slug "${slug}", найдено строк: ${rows.length}`);
|
||||
|
||||
if (rows.length === 0) {
|
||||
// Пробуем найти страницу без учета регистра
|
||||
const { rows: rowsCaseInsensitive } = await db.getQuery()(
|
||||
`SELECT * FROM ${tableName}
|
||||
WHERE LOWER(TRIM(slug)) = LOWER(TRIM($1))
|
||||
AND visibility = 'public'
|
||||
AND status = 'published'
|
||||
LIMIT 1`,
|
||||
[slug]
|
||||
);
|
||||
|
||||
if (rowsCaseInsensitive.length > 0) {
|
||||
console.log(`[pages] GET /published/:slug: найдено с учетом регистра, slug в БД: "${rowsCaseInsensitive[0].slug}"`);
|
||||
return res.json(rowsCaseInsensitive[0]);
|
||||
}
|
||||
|
||||
return res.status(404).json({ error: 'Страница не найдена' });
|
||||
}
|
||||
|
||||
console.log(`[pages] GET /published/:slug: страница найдена, id: ${rows[0].id}, slug: ${rows[0].slug}`);
|
||||
|
||||
// Расшифровываем зашифрованные поля
|
||||
const page = rows[0];
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Создаем объект с расшифрованными данными
|
||||
const decryptedPage = { ...page };
|
||||
|
||||
// Расшифровываем поля, если они зашифрованы
|
||||
const fieldsToDecrypt = ['title', 'summary', 'content', 'seo', 'settings'];
|
||||
for (const field of fieldsToDecrypt) {
|
||||
const encryptedField = `${field}_encrypted`;
|
||||
if (page[encryptedField]) {
|
||||
try {
|
||||
const decryptResult = await db.getQuery()(
|
||||
`SELECT decrypt_text($1, $2) as ${field}`,
|
||||
[page[encryptedField], encryptionKey]
|
||||
);
|
||||
if (decryptResult.rows[0] && decryptResult.rows[0][field] !== null) {
|
||||
decryptedPage[field] = decryptResult.rows[0][field];
|
||||
}
|
||||
} catch (decryptError) {
|
||||
console.warn(`[pages] GET /published/:slug: ошибка расшифровки поля ${field}:`, decryptError.message);
|
||||
if (page[field]) {
|
||||
decryptedPage[field] = page[field];
|
||||
}
|
||||
}
|
||||
} else if (page[field]) {
|
||||
decryptedPage[field] = page[field];
|
||||
}
|
||||
}
|
||||
|
||||
res.json(decryptedPage);
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения публичной страницы по slug:', error);
|
||||
res.status(500).json({ error: 'Ошибка получения страницы' });
|
||||
}
|
||||
});
|
||||
|
||||
// Получить иерархическую структуру всех публичных страниц
|
||||
router.get('/public/structure', async (req, res) => {
|
||||
try {
|
||||
@@ -1903,7 +2004,12 @@ router.get('/internal/all', async (req, res) => {
|
||||
});
|
||||
|
||||
// Получить одну опубликованную страницу по id
|
||||
router.get('/public/:id', async (req, res) => {
|
||||
router.get('/public/:id', async (req, res, next) => {
|
||||
// Пропускаем специальные endpoints
|
||||
if (req.params.id === 'robots.txt' || req.params.id === 'sitemap.xml') {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
const tableName = `admin_pages_simple`;
|
||||
|
||||
@@ -1950,7 +2056,7 @@ Disallow: /admin/
|
||||
Disallow: /content/create
|
||||
Disallow: /content/edit
|
||||
|
||||
Sitemap: ${baseUrl}/pages/public/sitemap.xml
|
||||
Sitemap: ${baseUrl}/api/pages/public/sitemap.xml
|
||||
`;
|
||||
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
@@ -2005,7 +2111,8 @@ router.get('/public/sitemap.xml', async (req, res) => {
|
||||
|
||||
// Добавляем страницы блога с использованием slug
|
||||
for (const page of blogPages) {
|
||||
const lastmod = page.updated_at || page.created_at || new Date().toISOString();
|
||||
const dateObj = page.updated_at || page.created_at || new Date();
|
||||
const lastmod = dateObj instanceof Date ? dateObj.toISOString() : String(dateObj);
|
||||
const pageUrl = page.slug
|
||||
? `${baseUrl}/blog/${page.slug}`
|
||||
: `${baseUrl}/blog?page=${page.id}`;
|
||||
@@ -2021,22 +2128,25 @@ router.get('/public/sitemap.xml', async (req, res) => {
|
||||
|
||||
// Получаем остальные публичные страницы (без show_in_blog)
|
||||
const { rows: otherPages } = await db.getQuery()(`
|
||||
SELECT id, updated_at, created_at
|
||||
SELECT id, slug, updated_at, created_at
|
||||
FROM ${tableName}
|
||||
WHERE status = 'published' AND visibility = 'public' AND (show_in_blog IS NULL OR show_in_blog = FALSE)
|
||||
ORDER BY created_at DESC
|
||||
`);
|
||||
|
||||
// Добавляем остальные публичные страницы
|
||||
// Добавляем остальные публичные страницы с использованием slug
|
||||
for (const page of otherPages) {
|
||||
const lastmod = page.updated_at || page.created_at || new Date().toISOString();
|
||||
const pageUrl = `${baseUrl}/content/published?page=${page.id}`;
|
||||
const dateObj = page.updated_at || page.created_at || new Date();
|
||||
const lastmod = dateObj instanceof Date ? dateObj.toISOString() : String(dateObj);
|
||||
const pageUrl = page.slug
|
||||
? `${baseUrl}/content/published/${page.slug}`
|
||||
: `${baseUrl}/content/published?page=${page.id}`;
|
||||
|
||||
sitemap += ` <url>
|
||||
<loc>${escapeXml(pageUrl)}</loc>
|
||||
<lastmod>${lastmod.split('T')[0]}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -139,6 +139,26 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Похожие статьи (только для блога) -->
|
||||
<section v-if="page && isBlogPage && relatedArticles.length > 0" class="related-articles">
|
||||
<h3 class="related-title">
|
||||
<i class="fas fa-newspaper"></i>
|
||||
Читайте также
|
||||
</h3>
|
||||
<div class="related-grid">
|
||||
<article
|
||||
v-for="article in relatedArticles"
|
||||
:key="article.id"
|
||||
class="related-card"
|
||||
@click="openRelatedArticle(article)"
|
||||
>
|
||||
<h4 class="related-card-title">{{ article.title }}</h4>
|
||||
<p v-if="article.summary" class="related-card-summary">{{ truncateSummary(article.summary) }}</p>
|
||||
<span class="related-card-link">Читать →</span>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Загрузка -->
|
||||
<div v-if="!page && isLoading" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
@@ -177,6 +197,10 @@ const props = defineProps({
|
||||
hideBackButton: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isPublishedRoute: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
@@ -192,6 +216,10 @@ const page = ref(null);
|
||||
const navigation = ref(null);
|
||||
const breadcrumbs = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const relatedArticles = ref([]);
|
||||
|
||||
// Определяем, это страница блога
|
||||
const isBlogPage = computed(() => route.path.startsWith('/blog'));
|
||||
|
||||
// Установка мета-тегов для SEO
|
||||
function updateMetaTags(pageData) {
|
||||
@@ -330,27 +358,31 @@ async function loadPage() {
|
||||
isLoading.value = true;
|
||||
|
||||
// Определяем, это slug или id
|
||||
// Проверяем, находимся ли мы на странице блога
|
||||
// Проверяем, находимся ли мы на странице блога или published
|
||||
const isBlogRoute = route.path.startsWith('/blog');
|
||||
const isPublishedSlugRoute = props.isPublishedRoute || route.path.startsWith('/content/published/');
|
||||
|
||||
// Если это строка и не чисто число, или мы на странице блога - считаем это slug
|
||||
const isSlug = isBlogRoute || (typeof props.pageId === 'string' && !/^\d+$/.test(props.pageId));
|
||||
// Если это строка и не чисто число, или мы на странице блога/published - считаем это slug
|
||||
const isSlug = isBlogRoute || isPublishedSlugRoute || (typeof props.pageId === 'string' && !/^\d+$/.test(props.pageId));
|
||||
|
||||
console.log('[DocsContent] loadPage:', {
|
||||
pageId: props.pageId,
|
||||
isBlogRoute,
|
||||
isPublishedSlugRoute,
|
||||
isSlug,
|
||||
routePath: route.path
|
||||
});
|
||||
|
||||
if (isSlug) {
|
||||
// Загружаем по slug через новый endpoint блога
|
||||
// Если pageId это число, но мы на странице блога, конвертируем в строку
|
||||
// Загружаем по slug
|
||||
const slug = typeof props.pageId === 'string' ? props.pageId : String(props.pageId);
|
||||
console.log('[DocsContent] Загрузка по slug:', slug);
|
||||
console.log('[DocsContent] Загрузка по slug:', slug, 'isPublishedSlugRoute:', isPublishedSlugRoute);
|
||||
|
||||
try {
|
||||
const response = await pagesService.getBlogPageBySlug(slug);
|
||||
// Используем разные endpoints для blog и published
|
||||
const response = isPublishedSlugRoute
|
||||
? await pagesService.getPublishedPageBySlug(slug)
|
||||
: await pagesService.getBlogPageBySlug(slug);
|
||||
console.log('[DocsContent] Ответ от API:', {
|
||||
hasData: !!response,
|
||||
type: typeof response,
|
||||
@@ -411,6 +443,11 @@ async function loadPage() {
|
||||
console.error('[DocsContent] Ошибка установки мета-тегов (не критично):', metaError);
|
||||
// Продолжаем работу, даже если мета-теги не установились
|
||||
}
|
||||
|
||||
// Загружаем похожие статьи для блога
|
||||
if (isBlogPage.value) {
|
||||
loadRelatedArticles();
|
||||
}
|
||||
} else {
|
||||
console.error('[DocsContent] page.value пусто после загрузки!', {
|
||||
response: 'данные не были установлены',
|
||||
@@ -629,6 +666,38 @@ function formatDate(date) {
|
||||
});
|
||||
}
|
||||
|
||||
// Загрузка похожих статей для блога
|
||||
async function loadRelatedArticles() {
|
||||
if (!isBlogPage.value || !page.value) return;
|
||||
|
||||
try {
|
||||
const allArticles = await pagesService.getBlogPages();
|
||||
// Исключаем текущую статью и берём до 3 других
|
||||
relatedArticles.value = allArticles
|
||||
.filter(article => article.id !== page.value.id)
|
||||
.slice(0, 3);
|
||||
} catch (e) {
|
||||
console.error('[DocsContent] Ошибка загрузки похожих статей:', e);
|
||||
relatedArticles.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Обрезка summary для карточек
|
||||
function truncateSummary(text, maxLength = 100) {
|
||||
if (!text) return '';
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength).trim() + '...';
|
||||
}
|
||||
|
||||
// Открытие похожей статьи
|
||||
function openRelatedArticle(article) {
|
||||
if (article.slug) {
|
||||
router.push({ name: 'blog-article', params: { slug: article.slug } });
|
||||
} else if (article.id) {
|
||||
router.push({ name: 'blog', query: { page: article.id } });
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(path) {
|
||||
// Поддержка разных форматов путей
|
||||
const match1 = path.match(/\/content\/published\/(\d+)/);
|
||||
@@ -1280,5 +1349,79 @@ onMounted(() => {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
/* Похожие статьи */
|
||||
.related-articles {
|
||||
margin-top: 48px;
|
||||
padding-top: 32px;
|
||||
border-top: 1px solid var(--border-color, #e5e7eb);
|
||||
}
|
||||
|
||||
.related-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #111827);
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.related-title i {
|
||||
color: var(--primary-color, #3b82f6);
|
||||
}
|
||||
|
||||
.related-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.related-card {
|
||||
background: var(--bg-secondary, #f9fafb);
|
||||
border: 1px solid var(--border-color, #e5e7eb);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.related-card:hover {
|
||||
border-color: var(--primary-color, #3b82f6);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.related-card-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #111827);
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.related-card-summary {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
margin: 0 0 12px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.related-card-link {
|
||||
font-size: 0.875rem;
|
||||
color: var(--primary-color, #3b82f6);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.related-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.related-articles {
|
||||
margin-top: 32px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -213,6 +213,11 @@ const routes = [
|
||||
name: 'content-published',
|
||||
component: () => import('../views/content/PublishedListView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/content/published/:slug',
|
||||
name: 'content-published-slug',
|
||||
component: () => import('../views/content/PublishedPageView.vue'),
|
||||
},
|
||||
{
|
||||
path: '/content/internal',
|
||||
name: 'content-internal',
|
||||
|
||||
@@ -83,6 +83,17 @@ export default {
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
async getPublishedPageBySlug(slug) {
|
||||
console.log('[pagesService] getPublishedPageBySlug:', slug);
|
||||
const res = await api.get(`/pages/published/${encodeURIComponent(slug)}`);
|
||||
console.log('[pagesService] getPublishedPageBySlug response:', {
|
||||
status: res.status,
|
||||
hasData: !!res.data,
|
||||
id: res.data?.id,
|
||||
title: res.data?.title
|
||||
});
|
||||
return res.data;
|
||||
},
|
||||
async getPublicPagesStructure() {
|
||||
const res = await api.get('/pages/public/structure');
|
||||
return res.data;
|
||||
|
||||
192
frontend/src/views/content/PublishedPageView.vue
Normal file
192
frontend/src/views/content/PublishedPageView.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<!--
|
||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
All rights reserved.
|
||||
|
||||
This software is proprietary and confidential.
|
||||
Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
For licensing inquiries: info@hb3-accelerator.com
|
||||
Website: https://hb3-accelerator.com
|
||||
GitHub: https://github.com/VC-HB3-Accelerator
|
||||
-->
|
||||
|
||||
<template>
|
||||
<BaseLayout :is-authenticated="isAuthenticated" :identities="identities" :token-balances="tokenBalances" :is-loading-tokens="isLoadingTokens" @auth-action-completed="$emit('auth-action-completed')">
|
||||
<div class="docs-page">
|
||||
<div class="docs-header">
|
||||
<button class="close-btn" @click="goBack" title="Закрыть">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент: сайдбар + контент -->
|
||||
<div class="docs-layout has-content">
|
||||
<!-- Сайдбар навигации -->
|
||||
<DocsSidebar :current-page-id="currentPageId" />
|
||||
|
||||
<!-- Основной контент -->
|
||||
<div class="docs-main">
|
||||
<DocsContent v-if="pageSlug" :page-id="pageSlug" :is-published-route="true" @back="goToIndex" />
|
||||
|
||||
<div v-else class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Загрузка документа...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import BaseLayout from '../../components/BaseLayout.vue';
|
||||
import DocsSidebar from '../../components/docs/DocsSidebar.vue';
|
||||
import DocsContent from '../../components/docs/DocsContent.vue';
|
||||
import pagesService from '../../services/pagesService';
|
||||
|
||||
const props = defineProps({
|
||||
isAuthenticated: { type: Boolean, default: false },
|
||||
identities: { type: Array, default: () => [] },
|
||||
tokenBalances: { type: Object, default: () => ({}) },
|
||||
isLoadingTokens: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const currentPageId = ref(null);
|
||||
const pageSlug = computed(() => route.params.slug);
|
||||
|
||||
function goBack() {
|
||||
router.push({ name: 'content-published' });
|
||||
}
|
||||
|
||||
function goToIndex() {
|
||||
router.push({ name: 'content-published' });
|
||||
}
|
||||
|
||||
// Загружаем ID страницы по slug для сайдбара
|
||||
onMounted(async () => {
|
||||
if (pageSlug.value) {
|
||||
try {
|
||||
const page = await pagesService.getPublishedPageBySlug(pageSlug.value);
|
||||
if (page && page.id) {
|
||||
currentPageId.value = page.id;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[PublishedPageView] Ошибка загрузки страницы:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.docs-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 40px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.docs-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
padding: 20px;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
background: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: #888;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.docs-layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.docs-main {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid var(--color-primary);
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.docs-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.docs-main {
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.docs-page {
|
||||
height: auto;
|
||||
min-height: calc(100vh - 40px);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.docs-layout {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.docs-layout.has-content .docs-sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.docs-main {
|
||||
overflow-y: visible;
|
||||
min-height: auto;
|
||||
width: 100%;
|
||||
display: block;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.docs-header {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user