feat: enhance blog functionality with new components and styles
- Added styles for WordPress table and button blocks in globals.css. - Updated layout.tsx to include a favicon. - Improved blog page layout and search functionality, including new search and filter icons. - Implemented related posts feature in blog post pages with new BlogBlockquote and BlogPullquote components. - Enhanced blog card display with a new variant for smaller cards. - Updated blog collapse sections for better user interaction. - Added new utility functions for extracting block data from WordPress content.
This commit is contained in:
+81
-8
@@ -12,7 +12,7 @@ import type { Element } from "domhandler";
|
||||
// ─── انواع بلاک (برای گسترش بعدی) ───────────────────────────────────────────
|
||||
|
||||
/** نام نوع بلاک؛ با افزودن بلاک جدید این یونیون را گسترش دهید. */
|
||||
export type BlockType = "collapse" | "blockquote" | "code";
|
||||
export type BlockType = "collapse" | "blockquote" | "pullquote" | "code";
|
||||
|
||||
/** Payload بلاک collapse (سوالات متداول / accordion). */
|
||||
export interface CollapseBlockPayload {
|
||||
@@ -20,14 +20,21 @@ export interface CollapseBlockPayload {
|
||||
items: { question: string; answer: string }[];
|
||||
}
|
||||
|
||||
/** Payload بلاک blockquote — بعداً پیادهسازی میشود. */
|
||||
// export interface BlockquoteBlockPayload { ... }
|
||||
/** Payload بلاک blockquote (نقلقول و منبع). content به صورت HTML است و باید با dangerouslySetInnerHTML رندر شود. */
|
||||
export interface BlockquoteBlockPayload {
|
||||
/** HTML محتوای نقلقول (بدون تگ cite). */
|
||||
content: string;
|
||||
citation?: string;
|
||||
}
|
||||
|
||||
/** Payload بلاک code — بعداً پیادهسازی میشود. */
|
||||
// export interface CodeBlockPayload { ... }
|
||||
|
||||
/** یونیون همهٔ payloadها برای تایپامن رندر. */
|
||||
export type BlockPayload = CollapseBlockPayload; // | BlockquoteBlockPayload | CodeBlockPayload
|
||||
export type BlockPayload =
|
||||
| CollapseBlockPayload
|
||||
| BlockquoteBlockPayload;
|
||||
// | CodeBlockPayload
|
||||
|
||||
// ─── سگمنت خروجی برای لایه رندر ─────────────────────────────────────────────
|
||||
|
||||
@@ -84,6 +91,58 @@ function extractCollapsePayload(
|
||||
return { title, items };
|
||||
}
|
||||
|
||||
/**
|
||||
* استخراج از بلاک آکاردئون وردپرس (core/accordion):
|
||||
* div.wp-block-accordion > div.wp-block-accordion-item با
|
||||
* .wp-block-accordion-heading__toggle-title (سؤال) و .wp-block-accordion-panel (جواب به صورت HTML).
|
||||
*/
|
||||
function extractAccordionPayload(
|
||||
$: cheerio.CheerioAPI,
|
||||
el: Element
|
||||
): CollapseBlockPayload {
|
||||
const $el = $(el);
|
||||
const items: { question: string; answer: string }[] = [];
|
||||
|
||||
$el.find(".wp-block-accordion-item").each((_: number, itemEl: Element) => {
|
||||
const $item = $(itemEl);
|
||||
const question = $item.find(".wp-block-accordion-heading__toggle-title").first().text().trim();
|
||||
const $panel = $item.find(".wp-block-accordion-panel").first();
|
||||
const answer = ($panel.html() ?? $panel.text() ?? "").trim();
|
||||
if (question || answer) items.push({ question, answer });
|
||||
});
|
||||
|
||||
return { title: "", items };
|
||||
}
|
||||
|
||||
function extractBlockquotePayload(
|
||||
$: cheerio.CheerioAPI,
|
||||
el: Element
|
||||
): BlockquoteBlockPayload {
|
||||
const $el = $(el);
|
||||
const citation = $el.find("cite").first().text().trim() || undefined;
|
||||
const $body = $el.clone();
|
||||
$body.find("cite").remove();
|
||||
const content = ($body.html() ?? "").trim();
|
||||
return { content, citation };
|
||||
}
|
||||
|
||||
/**
|
||||
* استخراج از بلاک pullquote وردپرس: figure.wp-block-pullquote > blockquote > p + cite.
|
||||
* همان شکل blockquote (content HTML + citation).
|
||||
*/
|
||||
function extractPullquotePayload(
|
||||
$: cheerio.CheerioAPI,
|
||||
el: Element
|
||||
): BlockquoteBlockPayload {
|
||||
const $el = $(el);
|
||||
const $blockquote = $el.find("blockquote").first();
|
||||
const citation = $blockquote.find("cite").first().text().trim() || undefined;
|
||||
const $body = $blockquote.clone();
|
||||
$body.find("cite").remove();
|
||||
const content = ($body.html() ?? "").trim();
|
||||
return { content, citation };
|
||||
}
|
||||
|
||||
/**
|
||||
* ثبت همهٔ بلاکهای شناختهشده.
|
||||
* برای بلاکهای جدید (مثلاً blockquote, code) فقط یک مورد جدید به این آرایه اضافه کنید.
|
||||
@@ -91,11 +150,24 @@ function extractCollapsePayload(
|
||||
export const BLOCK_DEFINITIONS: BlockDefinition[] = [
|
||||
{
|
||||
blockType: "collapse",
|
||||
selector: ".collpase-section",
|
||||
selector: "div.wp-block-group.collapse-section",
|
||||
extract: extractCollapsePayload as BlockDefinition["extract"],
|
||||
},
|
||||
// بعداً:
|
||||
// { blockType: "blockquote", selector: "blockquote.wp-block-quote", extract: extractBlockquotePayload },
|
||||
{
|
||||
blockType: "collapse",
|
||||
selector: "div.wp-block-accordion",
|
||||
extract: extractAccordionPayload as BlockDefinition["extract"],
|
||||
},
|
||||
{
|
||||
blockType: "blockquote",
|
||||
selector: "blockquote.wp-block-quote",
|
||||
extract: extractBlockquotePayload as BlockDefinition["extract"],
|
||||
},
|
||||
{
|
||||
blockType: "pullquote",
|
||||
selector: "figure.wp-block-pullquote",
|
||||
extract: extractPullquotePayload as BlockDefinition["extract"],
|
||||
},
|
||||
// { blockType: "code", selector: "pre.wp-block-code", extract: extractCodePayload },
|
||||
];
|
||||
|
||||
@@ -142,11 +214,12 @@ export function discoverBlocks(html: string): DiscoveredContent {
|
||||
const blocksByType: Record<string, BlockPayload[]> = {
|
||||
collapse: [],
|
||||
blockquote: [],
|
||||
pullquote: [],
|
||||
code: [],
|
||||
};
|
||||
|
||||
for (const def of BLOCK_DEFINITIONS) {
|
||||
const elements = $('.collpase-section').toArray();
|
||||
const elements = $(def.selector).toArray().filter(isElement);
|
||||
for (const el of elements) {
|
||||
const payload = def.extract($, el) as BlockPayload;
|
||||
const blockId = blocksByType[def.blockType].length;
|
||||
|
||||
@@ -132,6 +132,30 @@ export async function getPostBySlug(slug: string): Promise<WpPost | null> {
|
||||
return posts.length > 0 ? posts[0] : null;
|
||||
}
|
||||
|
||||
export async function getRelatedPosts(
|
||||
postId: number,
|
||||
categoryIds: number[],
|
||||
limit = 3
|
||||
): Promise<WpPost[]> {
|
||||
const params = new URLSearchParams();
|
||||
params.set("exclude", String(postId));
|
||||
params.set("per_page", String(limit));
|
||||
params.set("_embed", "1");
|
||||
if (categoryIds.length > 0) {
|
||||
params.set("categories", String(categoryIds[0]));
|
||||
}
|
||||
|
||||
const url = `${WP_API_BASE}/posts?${params.toString()}`;
|
||||
const res = await fetchWithTimeout(url);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
const posts: WpPost[] = await res.json();
|
||||
return posts;
|
||||
}
|
||||
|
||||
export async function getCategories(): Promise<WpCategory[]> {
|
||||
const url = `${WP_API_BASE}/categories?per_page=100`;
|
||||
const res = await fetchWithTimeout(url);
|
||||
|
||||
Reference in New Issue
Block a user