使用 ESNEXT 的古腾堡中继器块

Gutenberg repeater blocks using ESNEXT

我一直在努力创建我自己的带有文本和 link 输入字段的自定义 Gutenberg 转发器块。我只见过像 this and this 这样的 ES5 样本。我已经花了将近 8 个小时来创建我自己的这些示例版本,但我被卡住了。

我来这里是因为我想被指出正确的方向(非常需要帮助)。

这是我目前拥有的代码。我不知道从哪里开始进行 ES5 -> ESNEXT 转换。

编辑:我忘了说我正在努力避免为此使用 ACF

// Importing code libraries for this block
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';
import { RichText, MediaUpload, InspectorControls } from '@wordpress/block-editor';
import { Button, ColorPicker, ColorPalette, Panel, PanelBody, PanelRow } from '@wordpress/components';

registerBlockType('ccm-block/banner-block', {
    title: __('Banner Block'),
    icon: 'format-image', // from Dashicons → https://developer.wordpress.org/resource/dashicons/.
    category: 'layout', // E.g. common, formatting, layout widgets, embed.
    keywords: [
        __('Banner Block'),
        __('CCM Blocks'),
    ],
    attributes: {
        mediaID: {
            type: 'number'
        },
        mediaURL: {
            type: 'string'
        },
        title: {
            type: 'array',
            source: 'children',
            selector: 'h1'
        },
        content: {
            type: 'array',
            source: 'children',
            selector: 'p'
        },
        bannerButtons: {
            type: 'array',
            source: 'children',
            selector: '.banner-buttons',
        },
        items: {
            type: 'array',
            default: []
        }
    },

    /**
     * The edit function relates to the structure of the block when viewed in the editor.
     *
     * @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
     *
     * @param {Object} props Props.
     * @returns {Mixed} JSX Component.
     */
    edit: (props) => {

        const {
            attributes: { mediaID, mediaURL, title, content, bannerButtons },
            setAttributes, className
        } = props;

        const onSelectImage = (media) => {
            setAttributes({
                mediaURL: media.url,
                mediaID: media.id,
            });
        };

        const onChangeTitle = (value) => {
            setAttributes({ title: value });
        };

        const onChangeContent = (value) => {
            setAttributes({ content: value });
        };

        const onChangeBannerButtons = (value) => {
            setAttributes({ bannerButtons: value });
        };

        // console.log(items);
        // var itemList = items.sort

        return (
            <div className={className}>
                <div id="#home-banner">
                    <MediaUpload
                        onSelect={onSelectImage}
                        allowedTypes="image"
                        value={mediaID}
                        render={({ open }) => (
                            <Button className={mediaID ? 'image-button' : 'button button-large'} onClick={open}>
                                {!mediaID ? __('Upload Image', 'ccm-blocks') : <img src={mediaURL} alt={__('Featured Image', 'ccm-blocks')} />}
                            </Button>
                        )}
                    />

                    <RichText
                        tagName="h1"
                        placeholder={__('Insert Title Here', 'ccm-blocks')}
                        className={className}
                        onChange={onChangeTitle}
                        value={title}
                    />

                    <RichText
                        tagName="p"
                        placeholder={__('Insert your short description here...', 'ccm-blocks')}
                        className={className}
                        onChange={onChangeContent}
                        value={content}
                    />

                    <RichText
                        tagName="ul"
                        multiline="li"
                        className="banner-buttons"
                        placeholder={ __('Add a banner button link (max of 2)', 'ccm-blocks') }
                        onChange={ onChangeBannerButtons }
                        value={ bannerButtons }
                    />
                </div>
            </div>

        );
    },

    /**
     * The save function determines how the different attributes should be combined into the final markup.
     * Which is then serialised into the post_content.
     *
     * @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
     *
     * @param {Object} props Props.
     * @returns {Mixed} JSX Frontend HTML.
     */
    save: (props) => {
        return (
            <div className={ props.className }>
                <div id="home-banner" style={{backgroundImage: `url(${ props.attributes.mediaURL })`}}>
                    <div class="container">
                        <div class="row">
                            <div class="col-12">
                                <div class="content-inner">
                                    <RichText.Content tagName="h1" className={ props.className } value={ props.attributes.title } />
                                    <RichText.Content tagName="p" className={ props.className } value={ props.attributes.content } />
                                    <RichText.Content tagName="ul" className="banner-buttons" value={ props.attributes.bannerButtons } />
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    },
});

编辑 2:这是我失败的做法

// Importing code libraries for this block
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';
import { RichText, MediaUpload, InspectorControls } from '@wordpress/block-editor';
import { Button, ColorPicker, ColorPalette, Panel, PanelBody, PanelRow } from '@wordpress/components';

/**
 * Register the Block
 * 
 * @link https://wordpress.org/gutenberg/handbook/block-api/
 * @param  {string}   name     name.
 * @param  {Object}   settings settings.
 * @return {?WPBlock}          The block, otherwise `undefined`.
 */
registerBlockType('ccm-block/banner-block', {
    title: __('Banner Block'),
    icon: 'format-image', // from Dashicons → https://developer.wordpress.org/resource/dashicons/.
    category: 'layout', // E.g. common, formatting, layout widgets, embed.
    keywords: [
        __('Banner Block'),
        __('CCM Blocks'),
    ],
    attributes: {
        mediaID: {
            type: 'number'
        },
        mediaURL: {
            type: 'string'
        },
        title: {
            type: 'array',
            source: 'children',
            selector: 'h1'
        },
        content: {
            type: 'array',
            source: 'children',
            selector: 'p'
        },
        bannerButtons: {
            type: 'array',
            source: 'children',
            selector: '.banner-buttons',
        },
        items: {
            source: 'query',
            default: [],
            selector: '.item',
            query: {
                title: {
                    type: 'string',
                    source: 'text',
                    selector: '.title'
                },
                index: {            
                    type: 'number',
                    source: 'attribute',
                    attribute: 'data-index'            
                }           
            }
        }
    },

    /**
     * The edit function relates to the structure of the block when viewed in the editor.
     *
     * @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
     *
     * @param {Object} props Props.
     * @returns {Mixed} JSX Component.
     */
    edit: (props) => {

        const {
            attributes: { mediaID, mediaURL, title, content, bannerButtons, items },
            setAttributes, className
        } = props;

        const onSelectImage = (media) => {
            setAttributes({
                mediaURL: media.url,
                mediaID: media.id,
            });
        };

        const onChangeTitle = (value) => {
            setAttributes({ title: value });
        };

        const onChangeContent = (value) => {
            setAttributes({ content: value });
        };

        const onChangeBannerButtons = (value) => {
            setAttributes({ bannerButtons: value });
        };

        // Clone an array of objects
        function _cloneArray(arr) {
            if (Array.isArray(arr)) {
              for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
                arr2[i] = arr[i];
              }
              return arr2;
            } else {
              return Array.from(arr);
            }
        }
        // return repeater items
        var itemList = items.sort(function(a, b){
            return a.index - b.index;
        }).map(function(item){
            console.log(item);
            return(
                <RichText
                    tagName="h1"
                    placeholder={ __('Test', 'ccm-blocks') }
                    value={ item.title }
                    onChange={ function(value){
                        var newObject = Object.assign({}, item, {
                            title: value
                        });
                        setAttributes({
                            items: [].concat(_cloneArray(items.filter(function(itemFilter){
                                return itemFilter.index != item.index;
                            })), [newObject])
                        });
                    } }
                />
            );
        });

        // 
        console.log(itemList);

        return (
            <div className={className}>
                <div id="#home-banner">
                    <RichText
                        className="item-list"
                        tagName="h1"
                        value={ itemList }
                    />

                    <Button
                        className="button add-row"
                        onClick={ function(){
                            setAttributes({
                                items: [].concat(_cloneArray(items), [{
                                    index: items.length,
                                    title: ""
                                }])
                            });
                        } }
                    >
                        Add a button
                    </Button>

                    <MediaUpload
                        onSelect={onSelectImage}
                        allowedTypes="image"
                        value={mediaID}
                        render={({ open }) => (
                            <Button className={mediaID ? 'image-button' : 'button button-large'} onClick={open}>
                                {!mediaID ? __('Upload Image', 'ccm-blocks') : <img src={mediaURL} alt={__('Featured Image', 'ccm-blocks')} />}
                            </Button>
                        )}
                    />

                    <RichText
                        tagName="h1"
                        placeholder={__('Insert Title Here', 'ccm-blocks')}
                        className={className}
                        onChange={onChangeTitle}
                        value={title}
                    />

                    <RichText
                        tagName="p"
                        placeholder={__('Insert your short description here...', 'ccm-blocks')}
                        className={className}
                        onChange={onChangeContent}
                        value={content}
                    />

                    <RichText
                        tagName="ul"
                        multiline="li"
                        className="banner-buttons"
                        placeholder={ __('Add a banner button link (max of 2)', 'ccm-blocks') }
                        onChange={ onChangeBannerButtons }
                        value={ bannerButtons }
                    />
                </div>
            </div>

        );
    },

    /**
     * The save function determines how the different attributes should be combined into the final markup.
     * Which is then serialised into the post_content.
     *
     * @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
     *
     * @param {Object} props Props.
     * @returns {Mixed} JSX Frontend HTML.
     */
    save: (props) => {
        return (
            <div className={ props.className }>
                <div id="home-banner" style={{backgroundImage: `url(${ props.attributes.mediaURL })`}}>
                    <div class="container">
                        <div class="row">
                            <div class="col-12">
                                <div class="content-inner">
                                    <RichText.Content tagName="h1" className={ props.className } value={ props.attributes.title } />
                                    <RichText.Content tagName="p" className={ props.className } value={ props.attributes.content } />
                                    <RichText.Content tagName="ul" className="banner-buttons" value={ props.attributes.bannerButtons } />
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    },
});

我知道了!!经过无数小时的修补,这就是我想出的。这是我想要的粗略版本,但绝对有效!这是我找到的其中一个教程的 link link

// Importing code libraries for this block
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';
import { RichText } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';

// Register the block
registerBlockType( 'test-block/custom-repeater-block', {
    title: __('Repeater Block'),
    icon: 'layout',
    category: 'layout',
    keywords: [
        __('Custom Block'),
    ],
    attributes: {
        info: {
            type: 'array',
            selector: '.info-wrap'
        }
    },

    // edit function
    edit: (props) => {
        const {
            attributes: { info = [] },
            setAttributes, className
        } = props;

        const infoList = (value) => {
            return(
                value.sort((a, b) => a.index - b.index).map(infoItem => {
                    return(
                        <div className="info-item">
                            <Button
                                className="remove-item"
                                onClick={ () => {
                                    const newInfo = info.filter(item => item.index != infoItem.index).map(i => {
                                        if(i.index > infoItem.index){
                                            i.index -= 1;
                                        }
                                        return i;
                                    } );
                                    setAttributes({ info: newInfo });
                                } }
                            >&times;</Button>
                            <h3>Number {infoItem.index}</h3>
                            <RichText
                                tagName="h4"
                                className="info-item-title"
                                placeholder="Enter the title here"
                                value={infoItem.title}
                                style={{ height: 58 }}
                                onChange={ title => {
                                    const newObject = Object.assign({}, infoItem, {
                                        title: title
                                    });
                                    setAttributes({
                                        info: [...info.filter(
                                            item => item.index != infoItem.index
                                        ), newObject]
                                    });
                                } }
                            />
                            <RichText
                                tagName="p"
                                className="info-item-description"
                                placeholder="Enter description"
                                value={infoItem.description}
                                style={{ height: 58 }}
                                onChange={ description => {
                                    const newObject = Object.assign({}, infoItem, {
                                        description: description
                                    });
                                    setAttributes({
                                        info: [...info.filter(
                                            item => item.index != infoItem.index
                                        ), newObject]
                                    });
                                } }
                            />
                        </div>
                    )
                })
            )
        }

        return(
            <div className={className}>
                <div className="info-wrap">{infoList(info)}</div>
                <Button onClick={title => {
                    setAttributes({
                        info: [...info, {
                            index: info.length,
                            title: "",
                            description: ""
                        }]
                    });
                }}>Add Item</Button>
            </div>
        );
    },

    // save function
    save: (props) => {
        const info = props.attributes.info;
        const displayInfoList = (value) => {
            return(
                value.map( infoItem => {
                    return(
                        <div className="info-item">
                            <RichText.Content
                                tagName="h4"
                                className="info-item-title"
                                value={infoItem.title}
                                style={{ height: 58 }}
                            />
                        </div>
                    )
                } )
            )
        }

        return(
            <div className={props.className}>
                <div className="info-wrap">{ displayInfoList(info) }</div>
            </div>
        );
    }
} )

我刚刚创建了一个带有查询属性的示例,您可以在这里找到它:github repo

我相信在处理更复杂的块时查询是正确的方法。

所以第一个属性部分:

attributes: {
        services: {
            type: "array",
            source: "query",
            default: [],
            selector: "section .card-block",
            query: {
                index: {
                    type: "number",
                    source: "attribute",
                    attribute: "data-index",
                },
                headline: {
                    type: "string",
                    selector: "h3",
                    source: "text",
                },
                description: {
                    type: "string",
                    selector: ".card-content",
                    source: "text",
                },
            },
        },
    },

然后edit.js部分:

import produce from "immer";
import { __ } from "@wordpress/i18n";
import "./editor.scss";

import { RichText, PlainText } from "@wordpress/block-editor";
// import { useState } from "@wordpress/element";

export default function Edit({ attributes, setAttributes, className }) {
    const services = attributes.services;

    const onChangeContent = (content, index, type) => {
        const newContent = produce(services, (draftState) => {
            draftState.forEach((section) => {
                if (section.index === index) {
                    section[type] = content;
                }
            });
        });
        setAttributes({ services: newContent });
    };
    return (
        <>
            {services.map((service) => (
                <>
                    <RichText
                        tagName="h3"
                        className={className}
                        value={service.headline}
                        onChange={(content) =>
                            onChangeContent(content, service.index, "headline")
                        }
                    />
                    <PlainText
                        className={className}
                        value={service.description}
                        onChange={(content) =>
                            onChangeContent(content, service.index, "description")
                        }
                    />
                </>
            ))}
            <input
                className="button button-secondary"
                type="button"
                value={__("Add Service", "am2-gutenberg")}
                onClick={() =>
                    setAttributes({
                        services: [
                            ...attributes.services,
                            { headline: "", description: "", index: services.length },
                        ],
                    })
                }
            />
        </>
    );
}

最后,保存部分:

export default function save({ attributes, className }) {
    const { services } = attributes;

    return (
        <section className={className}>
            {services.length > 0 &&
                services.map((service) => {
                    return (
                        <div className="card-block" data-index={service.index}>
                            <h3>{service.headline}</h3>
                            <div className="card-content">{service.description}</div>
                        </div>
                    );
                })}
        </section>
    );
}