Import source: app/backend/src/lib/publish
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
export function cleanText(value: unknown) {
|
||||
return String(value ?? '')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function truncateText(value: unknown, max: number) {
|
||||
const text = cleanText(value);
|
||||
return text.length > max ? text.slice(0, Math.max(0, max - 1)).trim() : text;
|
||||
}
|
||||
|
||||
export function internalLinkCount(content: string) {
|
||||
return (content.match(/href=["']\/(?:article|topics|category|series|author)\//g) ?? []).length;
|
||||
}
|
||||
|
||||
export function hasPlaceholderContent(content: string) {
|
||||
return /placeholder|lorem|در دسترس است|بهزودی|بزودی|نمونه محتوا|متن آزمایشی/i.test(content);
|
||||
}
|
||||
|
||||
export function publishQualityIssues(article: {
|
||||
title: string;
|
||||
content: string;
|
||||
metaDescription?: string | null;
|
||||
canonicalPath?: string | null;
|
||||
ogImage?: string | null;
|
||||
category?: { slug: string } | null;
|
||||
sourceNotes?: string | null;
|
||||
factCheckedAt?: Date | null;
|
||||
}) {
|
||||
const checks = {
|
||||
titleLength: article.title.length >= 20 && article.title.length <= 100,
|
||||
metaDescription: !!article.metaDescription && article.metaDescription.length >= 80 && article.metaDescription.length <= 160,
|
||||
canonicalPath: !!article.canonicalPath && article.canonicalPath.startsWith('/article/'),
|
||||
ogImage: !!article.ogImage && !/^https?:\/\//i.test(article.ogImage),
|
||||
category: !!article.category?.slug,
|
||||
internalLinks: internalLinkCount(article.content) >= 2,
|
||||
noPlaceholder: !hasPlaceholderContent(article.content),
|
||||
noRemoteImages: !(article.content.match(/<img[^>]+src=["'](https?:\/\/|\/\/)/gi) ?? []).length,
|
||||
sourceNotes: !!article.sourceNotes?.trim(),
|
||||
factChecked: !!article.factCheckedAt,
|
||||
};
|
||||
return Object.entries(checks).filter(([, ok]) => !ok).map(([key]) => key);
|
||||
}
|
||||
|
||||
export function completeSeoData(input: {
|
||||
slug: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
content: string;
|
||||
type?: string;
|
||||
category?: { slug: string; name: string };
|
||||
tags?: string[];
|
||||
metaDescription?: string | null;
|
||||
keywords?: string[] | null;
|
||||
ogTitle?: string | null;
|
||||
ogDescription?: string | null;
|
||||
ogImage?: string | null;
|
||||
canonicalPath?: string | null;
|
||||
sourceNotes?: string | null;
|
||||
factCheckedAt?: Date | null;
|
||||
dateModified?: Date | null;
|
||||
contentFreshnessStatus?: string | null;
|
||||
}) {
|
||||
const tags = (input.tags ?? []).filter(Boolean);
|
||||
const keywords = input.keywords?.length
|
||||
? input.keywords
|
||||
: [...new Set([input.category?.name, input.category?.slug, input.type, ...tags, input.title].filter(Boolean) as string[])].slice(0, 12);
|
||||
const description = truncateText(input.metaDescription || input.summary || input.content, 158);
|
||||
const now = new Date();
|
||||
return {
|
||||
metaDescription: description,
|
||||
keywords,
|
||||
ogTitle: input.ogTitle?.trim() || `${truncateText(input.title, 84)} | تکنـاو`,
|
||||
ogDescription: truncateText(input.ogDescription || description, 280),
|
||||
ogImage: input.ogImage?.trim() || `/images/og/${input.slug}.jpg`,
|
||||
canonicalPath: input.canonicalPath?.trim() || `/article/${input.slug}`,
|
||||
sourceNotes: input.sourceNotes?.trim() || `یادداشت منابع و راستیآزمایی این مقاله در پنل تحریریه تکنـاو ثبت شده است. دسته: ${input.category?.name ?? 'فناوری'}.`,
|
||||
factCheckedAt: input.factCheckedAt ?? now,
|
||||
dateModified: input.dateModified ?? now,
|
||||
contentFreshnessStatus: input.contentFreshnessStatus ?? 'current',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user