admin管理员组文章数量:1122846
I've built the following Gutenberg block. It's a carousel of images:
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
G,
Path,
SVG,
ToolbarGroup,
ToolbarButton,
PanelBody,
__experimentalUnitControl as UnitControl,
__experimentalUseCustomUnits as useCustomUnits,
} from '@wordpress/components';
import {
InspectorControls,
BlockControls,
MediaUploadCheck,
MediaUpload,
MediaPlaceholder,
RichText,
useBlockProps,
useSettings
} from '@wordpress/block-editor';
import {
useRefEffect
} from '@wordpress/compose';
import { registerBlockType } from '@wordpress/blocks';
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import metadata from './block.json';
import { Carousel } from './carousel.js';
import './editor.scss';
import './style.scss';
const { name } = metadata;
export const settings = {
icon: {
src: <SVG viewBox="0 0 24 24" xmlns=";>
<Path fill="none" d="M0 0h24v24H0V0z" />
<G><Path d="M20 4v12H8V4h12m0-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 9.67l1.69 2.26 2.48-3.1L19 15H9zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z" /></G>
</SVG>,
foreground: '#ff8a00'
},
edit: (props => {
const blockProps = useBlockProps();
const {
attributes: { images, caption, max_height },
setAttributes,
} = props;
const units = useCustomUnits({
availableUnits: useSettings('spacing.units') || [
'px',
'em',
'rem',
'vw',
'vh',
],
defaultValues: { px: 100, em: 10, rem: 10, vw: 10, vh: 25 },
});
const displayImages = (images) => {
return (
images.map((image, index) => {
return (
<div className="carousel-item" key={index}>
<figure>
<img src={image.url} style={{ maxHeight: max_height }} alt={image.alt} key={image.id} />
{image.caption && (
<figcaption>{image.caption}</figcaption>
)}
</figure>
</div>
)
})
)
};
function setImages(media) {
setAttributes({ images: media })
}
function setCaption(value) {
setAttributes({ caption: value })
}
function setMaxHeight(value) {
setAttributes({ max_height: value })
}
const ref = useRefEffect((element) => {
Carousel(element, { speed: 10 });
}, []);
return (
<>
<InspectorControls>
<PanelBody
title={__('Settings', 'rather-simple-carousel')}
>
<UnitControl
label={__('Max height of elements', 'rather-simple-carousel')}
min="1"
onChange={setMaxHeight}
value={max_height}
units={units}
/>
</PanelBody>
</InspectorControls>
<BlockControls>
{images.length > 0 && (
<ToolbarGroup>
<MediaUploadCheck>
<MediaUpload
allowedTypes={['image']}
multiple={true}
gallery={true}
value={images.map((image) => image.id)}
onSelect={setImages}
render={({ open }) => (
<ToolbarButton onClick={open}>
{__('Edit images', 'rather-simple-carousel')}
</ToolbarButton>)}
/>
</MediaUploadCheck>
</ToolbarGroup>
)}
</BlockControls>
<MediaUploadCheck>
{images.length > 0 &&
<figure {...blockProps}>
<div className="carousel-wrapper" ref={ref}>
<div className="carousel-frame">
<div className="carousel-items">
{displayImages(images)}
</div>
</div>
<div className="carousel-arrow left"><span className="icon"><svg version="1.1" viewBox="0 0 24 24" xmlns=";><path d="m17.5 5v14l-11-7z" /></svg></span></div>
<div className="carousel-arrow right"><span className="icon"><svg version="1.1" viewBox="0 0 24 24" xmlns=";><path d="m6.5 5v14l11-7z" /></svg></span></div>
</div>
<RichText
className="carousel-caption"
tagName="figcaption"
placeholder={__('Enter a caption', 'rather-simple-carousel')}
value={caption}
onChange={setCaption}
/>
</figure>
}
{images.length === 0 &&
<MediaPlaceholder
accept="image/*"
allowedTypes={['image']}
onSelect={setImages}
multiple={true}
gallery={true}
addToGallery={true}
handleUpload={true}
labels={
{ title: __('Rather Simple Carousel', 'rather-simple-carousel') }
}
/>
}
</MediaUploadCheck>
</>
);
}),
};
registerBlockType(name, settings);
This is the corresponding block.json
file:
{
"$schema": ".json",
"apiVersion": 3,
"name": "my/rather-simple-carousel",
"title": "Rather Simple Carousel",
"description": "Display a carousel.",
"textdomain": "rather-simple-carousel",
"category": "media",
"attributes": {
"images": {
"type": "array",
"default": []
},
"caption": {
"type": "string"
},
"max_height": {
"type": "string",
"default": "300px"
}
},
"supports": {
"spacing": {
"margin": true,
"padding": true
}
},
"editorScript": "file:./index.js",
"viewScript": "file:./view.js",
"style": "file:./style-index.css",
"editorStyle": "file:./index.css"
}
The block works as expected with no issues. I need to add a transform to migrate a shortcode with the following syntax: [carousel id="n"]
, where id
is a post id representing a custom post type with the following meta fields:
_rsc_carousel_items
contains a string with attachment ids separated with commas (ex: 2220,2241,3256)
_rsc_carousel_max_height
contains a string with a height value in pixels (ex: 200px)
_rsc_carousel_caption
contains a string with a caption text
I added the following transform function to the block, but it's not working:
transforms: {
from: [
{
type: 'shortcode',
tag: 'carousel',
attributes: {
images: {
type: 'array',
source: async ({ id }) => {
const response = await apiFetch({ path: `/wp/v2/posts/${id}` });
const post = await response.json();
const imageIdsString = post.meta._rsc_carousel_items;
const imageIds = imageIdsString.split(',');
const images = await Promise.all(
imageIds.map(async (imageId) => {
const imageResponse = await apiFetch({
path: `/wp/v2/media/${imageId}`,
});
const image = await imageResponse.json();
return {
id: image.id,
url: image.source_url,
alt: image.alt_text,
caption: image.caption.rendered,
};
})
);
return images;
},
query: {
id: {
type: 'string',
shortcode: (attrs) => attrs.id,
},
},
},
caption: {
type: 'string',
source: async ({ id }) => {
const response = await apiFetch({ path: `/wp/v2/posts/${id}` });
const post = await response.json();
return post.meta._rsc_carousel_caption;
},
query: {
id: {
type: 'string',
shortcode: (attrs) => attrs.id,
},
},
},
max_height: {
type: 'string',
source: async ({ id }) => {
const response = await apiFetch({ path: `/wp/v2/posts/${id}` });
const post = await response.json();
return post.meta._rsc_carousel_max_height;
},
query: {
id: {
type: 'string',
shortcode: (attrs) => attrs.id,
},
},
}
},
},
],
},
Can anyone help me troubleshoot this? Thanks
I've built the following Gutenberg block. It's a carousel of images:
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
G,
Path,
SVG,
ToolbarGroup,
ToolbarButton,
PanelBody,
__experimentalUnitControl as UnitControl,
__experimentalUseCustomUnits as useCustomUnits,
} from '@wordpress/components';
import {
InspectorControls,
BlockControls,
MediaUploadCheck,
MediaUpload,
MediaPlaceholder,
RichText,
useBlockProps,
useSettings
} from '@wordpress/block-editor';
import {
useRefEffect
} from '@wordpress/compose';
import { registerBlockType } from '@wordpress/blocks';
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import metadata from './block.json';
import { Carousel } from './carousel.js';
import './editor.scss';
import './style.scss';
const { name } = metadata;
export const settings = {
icon: {
src: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<Path fill="none" d="M0 0h24v24H0V0z" />
<G><Path d="M20 4v12H8V4h12m0-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 9.67l1.69 2.26 2.48-3.1L19 15H9zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z" /></G>
</SVG>,
foreground: '#ff8a00'
},
edit: (props => {
const blockProps = useBlockProps();
const {
attributes: { images, caption, max_height },
setAttributes,
} = props;
const units = useCustomUnits({
availableUnits: useSettings('spacing.units') || [
'px',
'em',
'rem',
'vw',
'vh',
],
defaultValues: { px: 100, em: 10, rem: 10, vw: 10, vh: 25 },
});
const displayImages = (images) => {
return (
images.map((image, index) => {
return (
<div className="carousel-item" key={index}>
<figure>
<img src={image.url} style={{ maxHeight: max_height }} alt={image.alt} key={image.id} />
{image.caption && (
<figcaption>{image.caption}</figcaption>
)}
</figure>
</div>
)
})
)
};
function setImages(media) {
setAttributes({ images: media })
}
function setCaption(value) {
setAttributes({ caption: value })
}
function setMaxHeight(value) {
setAttributes({ max_height: value })
}
const ref = useRefEffect((element) => {
Carousel(element, { speed: 10 });
}, []);
return (
<>
<InspectorControls>
<PanelBody
title={__('Settings', 'rather-simple-carousel')}
>
<UnitControl
label={__('Max height of elements', 'rather-simple-carousel')}
min="1"
onChange={setMaxHeight}
value={max_height}
units={units}
/>
</PanelBody>
</InspectorControls>
<BlockControls>
{images.length > 0 && (
<ToolbarGroup>
<MediaUploadCheck>
<MediaUpload
allowedTypes={['image']}
multiple={true}
gallery={true}
value={images.map((image) => image.id)}
onSelect={setImages}
render={({ open }) => (
<ToolbarButton onClick={open}>
{__('Edit images', 'rather-simple-carousel')}
</ToolbarButton>)}
/>
</MediaUploadCheck>
</ToolbarGroup>
)}
</BlockControls>
<MediaUploadCheck>
{images.length > 0 &&
<figure {...blockProps}>
<div className="carousel-wrapper" ref={ref}>
<div className="carousel-frame">
<div className="carousel-items">
{displayImages(images)}
</div>
</div>
<div className="carousel-arrow left"><span className="icon"><svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m17.5 5v14l-11-7z" /></svg></span></div>
<div className="carousel-arrow right"><span className="icon"><svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m6.5 5v14l11-7z" /></svg></span></div>
</div>
<RichText
className="carousel-caption"
tagName="figcaption"
placeholder={__('Enter a caption', 'rather-simple-carousel')}
value={caption}
onChange={setCaption}
/>
</figure>
}
{images.length === 0 &&
<MediaPlaceholder
accept="image/*"
allowedTypes={['image']}
onSelect={setImages}
multiple={true}
gallery={true}
addToGallery={true}
handleUpload={true}
labels={
{ title: __('Rather Simple Carousel', 'rather-simple-carousel') }
}
/>
}
</MediaUploadCheck>
</>
);
}),
};
registerBlockType(name, settings);
This is the corresponding block.json
file:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my/rather-simple-carousel",
"title": "Rather Simple Carousel",
"description": "Display a carousel.",
"textdomain": "rather-simple-carousel",
"category": "media",
"attributes": {
"images": {
"type": "array",
"default": []
},
"caption": {
"type": "string"
},
"max_height": {
"type": "string",
"default": "300px"
}
},
"supports": {
"spacing": {
"margin": true,
"padding": true
}
},
"editorScript": "file:./index.js",
"viewScript": "file:./view.js",
"style": "file:./style-index.css",
"editorStyle": "file:./index.css"
}
The block works as expected with no issues. I need to add a transform to migrate a shortcode with the following syntax: [carousel id="n"]
, where id
is a post id representing a custom post type with the following meta fields:
_rsc_carousel_items
contains a string with attachment ids separated with commas (ex: 2220,2241,3256)
_rsc_carousel_max_height
contains a string with a height value in pixels (ex: 200px)
_rsc_carousel_caption
contains a string with a caption text
I added the following transform function to the block, but it's not working:
transforms: {
from: [
{
type: 'shortcode',
tag: 'carousel',
attributes: {
images: {
type: 'array',
source: async ({ id }) => {
const response = await apiFetch({ path: `/wp/v2/posts/${id}` });
const post = await response.json();
const imageIdsString = post.meta._rsc_carousel_items;
const imageIds = imageIdsString.split(',');
const images = await Promise.all(
imageIds.map(async (imageId) => {
const imageResponse = await apiFetch({
path: `/wp/v2/media/${imageId}`,
});
const image = await imageResponse.json();
return {
id: image.id,
url: image.source_url,
alt: image.alt_text,
caption: image.caption.rendered,
};
})
);
return images;
},
query: {
id: {
type: 'string',
shortcode: (attrs) => attrs.id,
},
},
},
caption: {
type: 'string',
source: async ({ id }) => {
const response = await apiFetch({ path: `/wp/v2/posts/${id}` });
const post = await response.json();
return post.meta._rsc_carousel_caption;
},
query: {
id: {
type: 'string',
shortcode: (attrs) => attrs.id,
},
},
},
max_height: {
type: 'string',
source: async ({ id }) => {
const response = await apiFetch({ path: `/wp/v2/posts/${id}` });
const post = await response.json();
return post.meta._rsc_carousel_max_height;
},
query: {
id: {
type: 'string',
shortcode: (attrs) => attrs.id,
},
},
}
},
},
],
},
Can anyone help me troubleshoot this? Thanks
Share Improve this question edited Jun 3, 2024 at 23:51 leemon asked Jun 2, 2024 at 18:00 leemonleemon 2,0024 gold badges22 silver badges51 bronze badges 10 | Show 5 more comments1 Answer
Reset to default 1I'm sharing my solution here in case it can help someone, as I believe this type of shortcode transform is a common use case.
As fetching data inside a transform is not possible right now, I'm creating the block inside the transform
by passing just the carousel id
attribute from the carousel
shortcode. I then fill the rest of the attributes afterwards inside the edit
function by querying the WP data store there. Finally, I set the temporary id
block attribute to undefined
so it's not stored in the block markup as this attribute is useless after the transform.
I know this is a bit convoluted and cumbersome, but it seems to be the only way to do this right now. If anyone finds a better way to do this, I'm all ears.
Thanks to Tom J Nowell for the help.
block.json:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my/rather-simple-carousel",
"title": "Rather Simple Carousel",
"description": "Display a carousel.",
"textdomain": "rather-simple-carousel",
"category": "media",
"attributes": {
"id": {
"type": "integer"
},
"images": {
"type": "array",
"items": {
"type": "integer"
},
"default": []
},
"caption": {
"type": "string",
"default": ""
},
"max_height": {
"type": "string",
"default": "300px"
}
},
"supports": {
"spacing": {
"margin": true,
"padding": true
}
},
"editorScript": "file:./index.js",
"viewScript": "file:./view.js",
"style": "file:./style-index.css",
"editorStyle": "file:./index.css"
}
index.js:
/**
* WordPress dependencies
*/
import {
G,
Path,
SVG,
} from '@wordpress/components';
import {
registerBlockType
} from '@wordpress/blocks';
/**
* Internal dependencies
*/
import metadata from './block.json';
import Edit from './edit';
import transforms from './transforms';
import './editor.scss';
import './style.scss';
const { name } = metadata;
export const settings = {
icon: {
src: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<Path fill="none" d="M0 0h24v24H0V0z" />
<G><Path d="M20 4v12H8V4h12m0-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 9.67l1.69 2.26 2.48-3.1L19 15H9zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z" /></G>
</SVG>,
foreground: '#ff8a00'
},
edit: Edit,
transforms,
};
registerBlockType(name, settings);
edit.js:
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
ToolbarGroup,
ToolbarButton,
PanelBody,
__experimentalUnitControl as UnitControl,
__experimentalUseCustomUnits as useCustomUnits,
} from '@wordpress/components';
import {
InspectorControls,
BlockControls,
MediaUploadCheck,
MediaUpload,
MediaPlaceholder,
RichText,
useBlockProps,
useSettings
} from '@wordpress/block-editor';
import { useEffect } from '@wordpress/element';
import { useRefEffect } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import { Carousel } from './carousel.js';
const Edit = (props) => {
const blockProps = useBlockProps();
const {
attributes: { id, images, caption, max_height },
setAttributes,
} = props;
const units = useCustomUnits({
availableUnits: useSettings('spacing.units') || [
'px',
'em',
'rem',
'vw',
'vh',
],
defaultValues: { px: 100, em: 10, rem: 10, vw: 10, vh: 25 },
});
// Get the carousel post meta data using the temporary id attribute.
const { post, hasPostResolved } = useSelect(select => {
return {
post: select('core').getEntityRecord('postType', 'carousel', id, {
context: 'edit',
_fields: ['id', 'meta']
}),
hasPostResolved: select('core').hasFinishedResolution('getEntityRecord', ['postType', 'carousel', id, {
context: 'edit',
_fields: ['id', 'meta']
}]),
}
}, [id]);
// Fill the missing attributes after a shortcode-to-block transform.
useEffect(() => {
if (hasPostResolved && post) {
if (images.length === 0) {
const postImages = post.meta._rsc_carousel_items.split(',');
setAttributes({ images: postImages });
}
if (!caption) {
const postCaption = post.meta._rsc_carousel_caption;
setAttributes({ caption: postCaption });
}
if (max_height === '300px') {
const postMaxHeight = post.meta._rsc_carousel_max_height;
setAttributes({ max_height: postMaxHeight + 'px' });
}
// Unset the id attribute as it's no longer necessary.
setAttributes({ id: undefined });
}
}, [hasPostResolved, post]);
const { attachments } = useSelect(select => {
const query = {
include: images.join(','),
per_page: images.length,
orderby: 'include'
};
return {
attachments: select('core').getMediaItems(query, { context: 'view' }),
}
}, [images]);
const displayImages = (images) => {
return (
images.map((image, index) => {
return (
<div className="carousel-item" key={index}>
<figure>
<img src={image.source_url} style={{ maxHeight: max_height }} alt={image.alt_text} key={image.id} />
{image.caption && (
<figcaption>{image.caption.rendered}</figcaption>
)}
</figure>
</div>
)
})
)
};
function setImages(media) {
const imageIDs = media.map(image => image.id);
setAttributes({ images: imageIDs })
}
function setCaption(value) {
setAttributes({ caption: value })
}
function setMaxHeight(value) {
setAttributes({ max_height: value })
}
const ref = useRefEffect((element) => {
Carousel(element, { speed: 10 });
}, []);
return (
<>
<InspectorControls>
<PanelBody
title={__('Settings', 'rather-simple-carousel')}
>
<UnitControl
label={__('Max height of elements', 'rather-simple-carousel')}
min="1"
onChange={setMaxHeight}
value={max_height}
units={units}
/>
</PanelBody>
</InspectorControls>
<BlockControls>
{images.length > 0 && (
<ToolbarGroup>
<MediaUploadCheck>
<MediaUpload
allowedTypes={['image']}
multiple={true}
gallery={true}
value={images}
onSelect={setImages}
render={({ open }) => (
<ToolbarButton onClick={open}>
{__('Edit images', 'rather-simple-carousel')}
</ToolbarButton>)}
/>
</MediaUploadCheck>
</ToolbarGroup>
)}
</BlockControls>
<MediaUploadCheck>
{attachments && attachments.length > 0 ?
<figure {...blockProps}>
<div className="carousel-wrapper" ref={ref}>
<div className="carousel-frame">
<div className="carousel-items">
{
displayImages(attachments)
}
</div>
</div>
<div className="carousel-arrow left"><span className="icon"><svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m17.5 5v14l-11-7z" /></svg></span></div>
<div className="carousel-arrow right"><span className="icon"><svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m6.5 5v14l11-7z" /></svg></span></div>
</div>
<RichText
className="carousel-caption"
tagName="figcaption"
placeholder={__('Enter a caption', 'rather-simple-carousel')}
value={caption}
onChange={setCaption}
/>
</figure>
:
<MediaPlaceholder
accept="image/*"
allowedTypes={['image']}
onSelect={setImages}
multiple={true}
gallery={true}
addToGallery={true}
handleUpload={true}
labels={
{ title: __('Rather Simple Carousel', 'rather-simple-carousel') }
}
/>
}
</MediaUploadCheck>
</>
);
}
export default Edit;
transforms.js
/**
* WordPress dependencies
*/
import { createBlock } from '@wordpress/blocks';
const transforms = {
from: [
{
type: 'shortcode',
tag: 'carousel',
transform({ named: { id } }) {
return createBlock('my/rather-simple-carousel', {
id: id
});
}
},
],
}
export default transforms;
本文标签: javascriptHow to transform a shortcode into a block
版权声明:本文标题:javascript - How to transform a shortcode into a block 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736303578a1931935.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
apiFetch
it's going to make a brand new request even if the entity you need has already been fetched and stored in the editors data store, causing slowdowns and possibly other problems. It would be much safer and faster to query the WP Data store for this information, and if it isn't available it would fetch it for you. – Tom J Nowell ♦ Commented Jun 2, 2024 at 18:13wp.data.select('core').getEntityRecord()
to get the information but apparently one can't use hooks such asuseSelect
inside a transform. It throws aInvalid hook call. Hooks can only be called inside of the body of a function component
error. – leemon Commented Jun 3, 2024 at 12:47useSelect
isn't the only way to interact with WP Data, and is actually a somewhat recent introduction meant to replacewithSelect
, neither of which should be needed here, but if you refer to my other suggestions then there would be no need at all to fetch the posts, only the IDs would be necessary which you already have. Similarly you already have a function component to calluseSelect
from, the edit component – Tom J Nowell ♦ Commented Jun 3, 2024 at 14:45useEffect
if they're missing. Though storing the URL at all is a mistake that complicates things, better to store attachment IDs then generate the associated URLs on render instead of storing the URLs. ThoseapiFetch
calls are also going to yield bad performance – Tom J Nowell ♦ Commented Jun 9, 2024 at 20:31