使用 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 });
} }
>×</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>
);
}
我一直在努力创建我自己的带有文本和 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 });
} }
>×</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>
);
}