~/icsd.ir — bash
SYSTEM_ONLINE

🧩 فصل ۱۲: ساخت بلاک Gutenberg با React

Gutenberg Blocks نسل جدید ویرایشگر وردپرسه. توی این فصل یاد می‌گیریم چطور بلاک‌های اختصاصی بسازیم - از یه بلاک ساده Hello World تا بلاک‌های داینامیک.

Gutenberg Blocks نسل جدید ویرایشگر وردپرسه. توی این فصل یاد می‌گیریم چطور بلاک‌های اختصاصی بسازیم – از یه بلاک ساده Hello World تا بلاک‌های داینامیک.

🎯 چرا بلاک سفارشی بسازیم؟

بلاک‌های پیش‌فرض گوتنبرگ خوبن، ولی برای کارهای خاص نیاز به بلاک سفارشی داری:

  • نمایش دیتای خاص (مثل آخرین محصولات با ساختار اختصاصی)
  • UI سفارشی برای کاربر (CTA، تخفیف، شمارنده)
  • یکپارچگی با API خارجی
  • طراحی شرکتی منحصربه‌فرد

🛠 ابزارهای مورد نیاز

  • Node.js ۱۸+ (برای npm/npx)
  • npm یا yarn
  • @wordpress/scripts برای build و dev
  • یک ادیتور (VSCode پیشنهاد می‌شه)
💡

@wordpress/create-block

ساده‌ترین راه شروع، استفاده از پکیج رسمی @wordpress/create-block هست. با یه دستور همه‌چیز رو راه می‌اندازه.

🚀 ساخت اولین بلاک

قدم ۱: ساخت پروژه

terminal
# برو به پوشه plugins
cd wp-content/plugins/

# ساخت بلاک
npx @wordpress/create-block my-first-block

# سؤال‌ها رو جواب بده، ساختار خودکار ساخته می‌شه
cd my-first-block

# نصب وابستگی‌ها (خودکار انجام شده)
# اجرای dev mode
npm start

# build برای production
npm run build

ساختار پروژه

structure
my-first-block/
├── build/                    # خروجی build (خودکار)
├── src/
│   ├── block.json           # تعریف بلاک
│   ├── edit.js              # کامپوننت ویرایشگر
│   ├── save.js              # خروجی نهایی
│   ├── style.scss           # استایل front
│   ├── editor.scss          # استایل editor
│   └── index.js             # ثبت بلاک
├── my-first-block.php       # plugin file
├── package.json
└── readme.txt

فایل اصلی PHP

my-first-block.php
<?php
/**
 * Plugin Name: اولین بلاک من
 * Version:     1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) exit;

function create_block_my_first_block_block_init() {
    register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'create_block_my_first_block_block_init' );

📋 block.json

مرکز تعریف بلاک. وردپرس از این فایل اطلاعات بلاک رو می‌خونه.

src/block.json
{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "icsd/hero-section",
    "version": "1.0.0",
    "title": "بخش هیرو",
    "category": "design",
    "icon": "format-image",
    "description": "یه بخش هیرو زیبا با عنوان و توضیحات",
    "keywords": ["hero", "header", "banner"],
    "textdomain": "icsd",
    "attributes": {
        "title": {
            "type": "string",
            "default": "عنوان شما اینجا"
        },
        "subtitle": {
            "type": "string",
            "default": "توضیح کوتاه"
        },
        "bgColor": {
            "type": "string",
            "default": "#2271b1"
        }
    },
    "supports": {
        "html": false,
        "align": ["wide", "full"]
    },
    "editorScript": "file:./index.js",
    "editorStyle": "file:./index.css",
    "style": "file:./style-index.css"
}

🎯 Attributes – ذخیره داده‌های بلاک

هر چی کاربر در بلاک تنظیم می‌کنه (متن، رنگ، آدرس عکس) به‌صورت attribute ذخیره می‌شه.

edit.js – ویرایشگر

src/edit.js
import { __ } from '@wordpress/i18n';
import { 
    useBlockProps, 
    RichText,
    InspectorControls 
} from '@wordpress/block-editor';
import { 
    PanelBody, 
    ColorPicker 
} from '@wordpress/components';

export default function Edit( { attributes, setAttributes } ) {
    const { title, subtitle, bgColor } = attributes;
    
    const blockProps = useBlockProps( {
        style: { backgroundColor: bgColor }
    } );
    
    return (
        <>
            {/* پنل تنظیمات سمت راست */}
            <InspectorControls>
                <PanelBody title="تنظیمات هیرو">
                    <p>رنگ پس‌زمینه:</p>
                    <ColorPicker
                        color={ bgColor }
                        onChange={ ( newColor ) => 
                            setAttributes( { bgColor: newColor } ) 
                        }
                    />
                </PanelBody>
            </InspectorControls>
            
            {/* خود بلاک */}
            <div { ...blockProps }>
                <RichText
                    tagName="h1"
                    value={ title }
                    onChange={ ( newTitle ) => 
                        setAttributes( { title: newTitle } ) 
                    }
                    placeholder="عنوان..."
                />
                <RichText
                    tagName="p"
                    value={ subtitle }
                    onChange={ ( newSubtitle ) => 
                        setAttributes( { subtitle: newSubtitle } ) 
                    }
                    placeholder="توضیحات..."
                />
            </div>
        </>
    );
}

save.js – خروجی نهایی

src/save.js
import { useBlockProps, RichText } from '@wordpress/block-editor';

export default function save( { attributes } ) {
    const { title, subtitle, bgColor } = attributes;
    
    const blockProps = useBlockProps.save( {
        style: { backgroundColor: bgColor }
    } );
    
    return (
        <div { ...blockProps }>
            <RichText.Content tagName="h1" value={ title } />
            <RichText.Content tagName="p" value={ subtitle } />
        </div>
    );
}

index.js – ثبت بلاک

src/index.js
import { registerBlockType } from '@wordpress/blocks';
import './style.scss';

import Edit from './edit';
import save from './save';
import metadata from './block.json';

registerBlockType( metadata.name, {
    edit: Edit,
    save,
} );

⚡ بلاک داینامیک (Dynamic Block)

بلاک‌های داینامیک خروجی رو با PHP در زمان render تولید می‌کنن – برای داده‌های متغیر مثل آخرین نوشته‌ها مفیدن.

تفاوت Static و Dynamic

Static Dynamic
HTML در save.js تولید می‌شه HTML با PHP تولید می‌شه
توی محتوای پست ذخیره می‌شه هر بار از نو ساخته می‌شه
سرعت بیشتر (cache) داده‌های به‌روز
برای محتوای ثابت برای محتوای زنده

ساخت بلاک داینامیک

۱. block.json

block.json
{
    "name": "icsd/recent-products",
    "title": "آخرین محصولات",
    "category": "widgets",
    "render": "file:./render.php",
    "attributes": {
        "count": {
            "type": "number",
            "default": 4
        }
    }
}

۲. save.js (null برای داینامیک)

src/save.js
// برای بلاک داینامیک، save باید null برگردونه
export default function save() {
    return null;
}

۳. render.php

src/render.php
<?php
$count = $attributes['count'] ?? 4;

$query = new WP_Query( array(
    'post_type'      => 'icsd_product',
    'posts_per_page' => $count,
    'orderby'        => 'date',
    'order'          => 'DESC',
) );
?>

<div <?php echo get_block_wrapper_attributes(); ?>>
    <h3>آخرین محصولات</h3>
    <div class="products-grid">
        <?php while ( $query->have_posts() ) : $query->the_post(); ?>
            <article class="product">
                <a href="<?php the_permalink(); ?>">
                    <?php the_post_thumbnail( 'medium' ); ?>
                    <h4><?php the_title(); ?></h4>
                </a>
            </article>
        <?php endwhile; wp_reset_postdata(); ?>
    </div>
</div>

۴. edit.js (ویرایشگر)

src/edit.js
import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl } from '@wordpress/components';
import ServerSideRender from '@wordpress/server-side-render';

export default function Edit( { attributes, setAttributes } ) {
    const { count } = attributes;
    
    return (
        <div { ...useBlockProps() }>
            <InspectorControls>
                <PanelBody title="تنظیمات">
                    <RangeControl
                        label="تعداد محصول"
                        value={ count }
                        onChange={ ( newCount ) => 
                            setAttributes( { count: newCount } ) 
                        }
                        min={ 1 }
                        max={ 12 }
                    />
                </PanelBody>
            </InspectorControls>
            
            {/* پیش‌نمایش زنده */}
            <ServerSideRender
                block="icsd/recent-products"
                attributes={ attributes }
            />
        </div>
    );
}

ServerSideRender

این کامپوننت پیش‌نمایش زنده از خروجی PHP رو در ادیتور نشون می‌ده. هر بار که attribute تغییر کنه، خروجی به‌روز می‌شه.

🎯 مثال کامل: بلاک شمارنده

متوسط

بلاک شمارنده آماری (Counter)

یه بلاک که عددی نمایش می‌ده با لیبل (مثل تعداد مشتری، پروژه و…)

src/edit.js
import { useBlockProps, RichText, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';

export default function Edit( { attributes, setAttributes } ) {
    const { number, label, icon } = attributes;
    
    return (
        <>
            <InspectorControls>
                <PanelBody title="تنظیمات شمارنده">
                    <TextControl
                        label="آیکن (ایموجی)"
                        value={ icon }
                        onChange={ ( v ) => setAttributes( { icon: v } ) }
                    />
                </PanelBody>
            </InspectorControls>
            
            <div { ...useBlockProps( { className: 'counter-block' } ) }>
                <div className="counter-icon">{ icon }</div>
                <RichText
                    tagName="span"
                    className="counter-number"
                    value={ number }
                    onChange={ ( v ) => setAttributes( { number: v } ) }
                    placeholder="۱۰۰+"
                />
                <RichText
                    tagName="span"
                    className="counter-label"
                    value={ label }
                    onChange={ ( v ) => setAttributes( { label: v } ) }
                    placeholder="مشتری راضی"
                />
            </div>
        </>
    );
}

📦 ساخت Block Patterns

Pattern یعنی ترکیب چند بلاک از پیش آماده. کاربر فقط روی pattern کلیک می‌کنه و همه بلاک‌ها وارد صفحه می‌شن.

register pattern
function icsd_register_block_patterns() {
    register_block_pattern(
        'icsd/hero-with-cta',
        array(
            'title'       => 'هیرو با CTA',
            'description' => 'بخش هیرو با دکمه فراخوان عمل',
            'categories'  => array( 'icsd' ),
            'content'     => '
                <!-- wp:cover -->
                <div class="wp-block-cover">
                    <!-- wp:heading {"level":1} -->
                    <h1>با ICSD همراه شوید</h1>
                    <!-- /wp:heading -->
                </div>
                <!-- /wp:cover -->
            ',
        )
    );
}
add_action( 'init', 'icsd_register_block_patterns' );

📝 خلاصه فصل

  • برای ساخت بلاک: npx @wordpress/create-block
  • block.json مرکز تعریف بلاکه
  • edit.js → ویرایشگر، save.js → خروجی
  • برای داده‌های زنده، بلاک Dynamic بساز
  • InspectorControls → پنل سایدبار
  • RichText → ویرایش متن
  • ServerSideRender → پیش‌نمایش PHP در ویرایشگر

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.