وبلاگ و سئو: پیشنویس/انتشار مقاله + کلمه کلیدی سئو در دیتابیس + OG/canonical/JSON-LD و متای سمت سرور + تغییر رمز و محدودسازی ورود + تستها
- مقالهها ستون status (draft/published) گرفتند — GET /api/site و همه انتهای عمومی فقط منتشرشده را برمیگردانند؛ autosave دیگر پیشنویس نیمهکاره را زنده نمیکند - کلمه کلیدی هدف چکلیست سئو فیلد seo_keyphrase شد (قبلاً localStorage بود) - robots.txt و sitemap.xml از بکاند + سرو /blog و /projects با تزریق title/description/OG/JSON-LD از دیتابیس برای خزندههای بدون JS (app/seo_pages.py) - فرانت: applySeo در seo.ts (OG/توییتر/canonical/JSON-LD)، ادغام پیشنویس برای ادمین، برچسب پیشنویس در وبلاگ، حالت خالی وبلاگ، دکمه انتشار در صفحه نوشتن - تغییر رمز ادمین POST /api/auth/change-password + دیالوگ «رمز» در نوار مدیریت - محدودسازی ورود: ۵ تلاش ناموفق در ۱۵ دقیقه از هر IP → 429 - ساخت دکمه + برای ردیف سئوی صفحههای بدون ردیف (SingletonPill) - تستهای pytest (۱۵ مورد) + پینکردن requirements + حذف package.json سرگردان فرانت
This commit is contained in:
@@ -7,7 +7,7 @@ import { ButtonLink } from '../components/Button';
|
||||
import { ArticleCard } from '../components/ArticleCard';
|
||||
import { Newsletter } from '../components/home/Newsletter';
|
||||
import { useSite } from '../api';
|
||||
import { setMetaDescription } from '../seo';
|
||||
import { applySeo } from '../seo';
|
||||
import { useEffect } from 'react';
|
||||
import { ARTICLE_EXTENSIONS } from '../components/admin/ArticleEditor';
|
||||
import type { Article } from '../data/site';
|
||||
@@ -34,11 +34,27 @@ export function ArticleDetail() {
|
||||
const { articles, brand } = useSite();
|
||||
const article = articles.find((a) => a.slug === slug);
|
||||
|
||||
// عنوان تب و توضیح متا از خود مقاله
|
||||
// عنوان تب، توضیح متا، OG و JSON-LD از خود مقاله
|
||||
useEffect(() => {
|
||||
if (!article) return;
|
||||
document.title = `${article.title} | ${brand}`;
|
||||
setMetaDescription(article.excerpt);
|
||||
applySeo({
|
||||
title: `${article.title} | ${brand}`,
|
||||
description: article.excerpt,
|
||||
path: `/blog/${article.slug}`,
|
||||
image: article.image,
|
||||
type: 'article',
|
||||
jsonLd: {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Article',
|
||||
headline: article.title,
|
||||
description: article.excerpt,
|
||||
image: article.image ? new URL(article.image, window.location.origin).href : undefined,
|
||||
inLanguage: 'fa',
|
||||
author: { '@type': 'Organization', name: brand },
|
||||
publisher: { '@type': 'Organization', name: brand },
|
||||
mainEntityOfPage: new URL(`/blog/${article.slug}`, window.location.origin).href,
|
||||
},
|
||||
});
|
||||
}, [article?.id]);
|
||||
|
||||
if (!article) {
|
||||
@@ -55,7 +71,7 @@ export function ArticleDetail() {
|
||||
}
|
||||
|
||||
const { html, legacy } = renderBody(article);
|
||||
const related = articles.filter((a) => a.slug !== article.slug).slice(0, 3);
|
||||
const related = articles.filter((a) => a.slug !== article.slug && a.status !== 'draft').slice(0, 3);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user