Is there any way to extend the advanced field section in each Gutenberg blocks? I want to add more fields in each blocks. Please help.
New answer: Try this. I made this to add inline styling to my blocks. I used isAdmin to define wether admin or not, to prevent author or other users to add inline styles everywhere. In your
index.js
/**
* Inline Style component
*
* Shows a field for inline style only visible for admins
* and adds a style attribute to the save content of the block
*/
import './attributes.js';
import './inspector.js';
inspector.js
const { __ } = wp.i18n;
const { select } = wp.data;
const { createHigherOrderComponent } = wp.compose;
const { InspectorAdvancedControls } = wp.blockEditor;
const { TextareaControl } = wp.components;
const withInspectorControls = createHigherOrderComponent((BlockEdit) => {
return (props) => {
const { inlineStyle } = props.attributes;
const isAdmin = select( 'core' ).canUser( 'create', 'users' );
return (
<>
<BlockEdit {...props} />
{ isAdmin ?
<InspectorAdvancedControls key="inspector">
<TextareaControl
label={__('Inline styling', 'custom-blocks')}
help={ __('Notice: This inline style will override any other inline style generated by Gutenberg.', 'custom-blocks') }
value={inlineStyle}
onChange={inlineStyle => props.setAttributes({ inlineStyle })}
/>
</InspectorAdvancedControls>
: '' }
</>
);
};
}, 'withInspectorControl');
wp.hooks.addFilter('editor.BlockEdit', 'custom-blocks/inline-style/inspector', withInspectorControls);
attributes.js:
wp.hooks.addFilter('blocks.registerBlockType', 'custom-blocks/inline-style/attributes', function (settings, name) {
settings = window.lodash.assign({}, settings, {
attributes: window.lodash.assign({}, settings.attributes, {
inlineStyle: {
type: 'string',
default: "",
}
})
});
return settings;
});
wp.hooks.addFilter('blocks.getSaveContent.extraProps','custom-blocks/inline-style/inspector',function(props, name, atts){
if(atts['inlineStyle']!="")
return lodash.assign(props, { style: atts['inlineStyle'] });
return props;
});
Old answer:
As far as I know you can only add an input field to add and id to a block in the advanced field section.
However you can add custom sections to your inspector controls.
Basically there are two types of blocks: core blocks and custom blocks.
For custom blocks it is very simple, take a look at how to use a Panel
For core blocks, you need to use a filter:
blocks.registerBlockType: here you need to add attributes to the block you want to change
blocks.getSaveContent.extraProps: here you have to save the attributes
editor.BlockEdit: and here you can use the Panel to edit the block's inspector controls.
Related
I have used rich text element of Gutenberg, in editor i found this kind of entity coming when we are editing - https://prnt.sc/uqJZokTorIK_ , that is creating issue in layout, when we click its showing blank space.
/**
* WordPress dependencies
*/
import { __ } from "#wordpress/i18n";
import { RichText, useBlockProps } from "#wordpress/block-editor";
import { Platform } from "#wordpress/element";
export default function edit({ attributes, setAttributes }) {
const {circleHeadline,circleText} = attributes;
const blockProps = useBlockProps({
className: 'visual-circle__item',
});
const onTitleChange = (value) => {
const newTitle = { circleHeadline: value };
setAttributes(newTitle);
};
const onTextChange = (value) => {
const newText = { circleText: value };
setAttributes(newText);
};
return (
<li {...blockProps}>
<RichText
identifier="circleHeadline"
tagName="span"
className="visual-circle__title"
value={circleHeadline}
onChange={onTitleChange}
withoutInteractiveFormatting={true}
aria-label={__("Main Text")}
placeholder={__("Lorem")}
{...(Platform.isNative && { deleteEnter: true })} // setup RichText on native mobile to delete the "Enter" key as it's handled by the JS/RN side
allowedFormats={[""]}
/>
<RichText
identifier="circleText"
tagName="span"
className="visual-circle__text"
value={circleText}
onChange={onTextChange}
withoutInteractiveFormatting={true}
aria-label={__("Sub Text")}
placeholder={__("Nullam dictum eu pede")}
{...(Platform.isNative && { deleteEnter: true })} // setup RichText on native mobile to delete the "Enter" key as it's handled by the JS/RN side
allowedFormats={[""]}
/>
</li>
);
}
I have tried diffrent tag instead of span tag in rich text but issue is same.
I fixed this issue, i found that this character is coming in core blocks rich text element also, the issue in my code is i used span tag with display block css.
I am writing a React editor component, it could be used to write new posts or update an existing post.If used as an update editor, the props MUST receive title and content; if used as new post editor, title and content will not exist.
But I don't know how to let typescript access the "flag" props so that it could decide which interface it could use.
I want to use the component as follows:
<PostEditor flag="newPost"/>
<PostEditor
flag="updatePost"
title="a good story"
content="once upone a time"
/>
I write the interface as follows:
interface NewPostProps{
flag:"newPost",
}
interface UpdatePostProps{
flag:"updatePost",
title:string,
content:string,
}
type IPostEditorProps<T>= T extends "newPost"?NewPostProps:UpdatePostProps
I write the react component as this. It does not work. I want the flag props to be the generic type but I don't know how to write that.
export const PostEditor=({flag,title}:IPostEditorProps<T>)=>{
// component contents
return (<></>)
}
Thank you for your help.
Working example code:
interface CommonProps {
children?: React.ReactNode;
// ...other props that always exist
}
interface NewPostProps {
content: never;
flag: "newPost";
title: never;
}
interface UpdatePostProps {
content: string;
flag: "updatePost";
title: string;
}
type ConditionalProps = NewPostProps | UpdatePostProps;
type Props = CommonProps & ConditionalProps;
export const PostEditor = (props: Props): JSX.Element => {
const { children, content, flag, title } = props;
if (flag === "newPost") return <>{/* implement newpost */}</>;
return (
<div>
<h1>{title}</h1>
<p>{content}</p>
{children}
</div>
);
};
Useful link: conditional react props with typescript
If you have any questions, feel free to ask!
Using the example Storybook code at the bottom of this post, I expect to see a Primary button rendered containing the text, "Primary Button", but instead the button renders following code, verbatim:
(...args) => {
// If a user calls a compiled slot inside a template expression (#1745), it
// can mess up block tracking, so by default we disable block tracking and
// force bail out when invoking a compiled slot (indicated by the ._d flag).
// This isn't necessary if rendering a compiled `<slot>`, so we flip the
// ._d flag off when invoking the wrapped fn inside `renderSlot`.
if (renderFnWithContext._d) {
setBlockTracking(-1);
}
const prevInstance = setCurrentRenderingInstance(ctx);
const res = fn(...args);
setCurrentRenderingInstance(prevInstance);
if (renderFnWithContext._d) {
setBlockTracking(1);
}
if (true) {
devtoolsComponentUpdated(ctx);
}
return res;
}
How do I render Vue component slot content in Storybook?
The Storybook code:
// ./components/button/Button.stories.js
import AtxButton from './Button';
import ButtonTypes from './Button.types';
export default {
/* π The title prop is optional.
* See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'Button',
component: AtxButton,
argTypes: {
default: {
description: 'The default Vue slot',
control: 'text'
}
}
};
//π We create a βtemplateβ of how args map to rendering
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { AtxButton },
setup() {
//π The args will now be passed down to the template
return { args };
},
template: `
<AtxButton v-bind="args">{{ args.default }}
</AtxButton>
`
});
//π Each story then reuses that template
export const Primary = Template.bind({});
Primary.args = {
intensity: ButtonTypes.Intensity.Primary,
default: 'Primary Button'
};
export const Secondary = Template.bind({});
Secondary.args = {
intensity: ButtonTypes.Intensity.Secondary,
default: 'Secondary Button'
};
I had syntax error in my component code. The Button component is created using a render function, which includes slot content by accessing this.slots.default(). I was getting a console error claiming "this.slots.default() is not a function", so I changed it to this.slots.default, assuming it might have been a getter.
That wasn't the right solution, of course. I changed it back to this.slots.default(), and then ensured that slot content was always included in the Storybook story. Now it works perfectly!
If I want to extend the core block I can use Block Filters where I found a filter like editor.BlockEdit.
Example from doc:
const { createHigherOrderComponent } = wp.compose;
const { Fragment } = wp.element;
const { InspectorControls } = wp.blockEditor;
const { PanelBody } = wp.components;
const withInspectorControls = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
return (
<Fragment>
<BlockEdit { ...props } />
<InspectorControls>
<PanelBody>My custom control</PanelBody>
</InspectorControls>
</Fragment>
);
};
}, 'withInspectorControl' );
wp.hooks.addFilter(
'editor.BlockEdit',
'my-plugin/with-inspector-controls',
withInspectorControls
);
It works, but now i would like to remove unwanted panels from core blocks. Unfortunately, the documentation does not contain any such example so I can't understand how to do this.
So how to remove unwanted panels inside InspectorControls from core blocks in Gutenberg?
Note: I know about the newest feature theme.json, but it has very limited functions and won't remove everything it needs
I am creating my first ever self made web development project and have run into an infinite loop. The full project can be found on https://github.com/Olks95/my-dnd/tree/spellbook. So the question is: What causes the loop and how do I fix it?
(The loop happens somewhere in the 2nd item of the 'Playground' component when the ContentSelector - Spellbook is called. The custom hook useHandbook is called in Spellbook and continously calls the API, should obviously only happen once... refresh or click return to stop spamming )
From what I can tell the issue is not in the custom hook itself, as I have made several attempts to rewrite it and an empty dependency array is added to the end of the useEffect(). I will try to explain with example code here.
import { Component1, Component2, Component3 } from './ContentSelector.js';
const components = {
option1: Component1,
option2: Component2
option3: Component3
}
const Playground = (props) => {
const LeftItem = components['option1']
const MiddleItem = components['option2']
const RightItem = components['option3']
...
}
I wanted to be able to choose what content to put in each element and ended up making a ContentSelector component that has all the content components in one file, and individually imported/exported. This seems like a strange way to do it, but it was the only way I found to make it work. (Maybe the cause of the loop?) Since this is still fairly early on in the development the selection is hard coded. The item variables starts with a capital letter so I can later call them as components to render like so:
<LeftItem ...some properties... />
Playground then returns the following to be rendered:
return(
<React.Fragment>
<div className="container">
<div className="flex-item">
/* Working select-option to pass correct props to Component1 */
<div className="content">
<LeftItem ...some properties... />
</div>
</div
<div className="flex-item">
/* Currently the same selector that changes the content of the LeftItem */
<div className="content">
<MiddleItem ...some properties... />
</div>
</div>
/*RightItem follows the same formula but currently only renders "coming soon..." */
</div>
</React.Fragment>
)
The Content selector then has the three components where:
Component1: calls a custom hook that only runs once. The information is then sent to another component to render. All working fine.
Component2: calls a custom hook infinite times, but is expected to work the same way component 1 does...
Component3: Renders coming soon...
See Component1 and 2 below:
export const Component1 = (props) => {
const [ isLoading, fetchedData ] = useDicecloud(props.selectedChar);
let loadedCharacter = null;
if(fetchedData) {
loadedCharacter = {
name: fetchedData[0].Name,
alignment: fetchedData[0].Alignment,
/* a few more assignments */
};
}
let content = <p>Loading characters...</p>;
if(!isLoading && fetchedData && fetchedData.length > 0) {
content = (
<React.Fragment>
<Character
name={loadedCharacter.name}
alignment={loadedCharacter.alignment}
/* a few more props */ />
</React.Fragment>
)
}
return content;
}
export const Component2 = (props) => {
const [ fetchedData, error, isLoading ] = useHandbook('https://cors-anywhere.herokuapp.com/http://dnd5eapi.co/api/spells/?name=Aid')
let content = <p>Loading spells...</p>;
if(!isLoading && fetchedData) {
/* useHandbook used to return text to user in a string in some situations */
if(typeof fetchedData === 'string') {
content = (
<React.Fragment>
<p> {fetchedData} </p>
</React.Fragment>
)
} else {
content = (
<React.Fragment>
<Spellbook
/* the component that will in the future render the data from the API called in useHandbook */
/>
</React.Fragment>
)
}
}
return content;
}
I have been working on this issue for a few days and it is getting more confusing as I go along. I expected the mistake to be in useHandbook, but after many remakes it does not seem to be. The current useHandbook is very simple as shown below.
export const useHandbook = (url) => {
const [ isLoading, setIsLoading ] = useState(false);
const [ error, setError ] = useState(null);
const [ data, setData ] = useState(null);
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(url, {
method: "GET",
mode: 'cors'
});
const json = await res.json();
setData(json);
setIsLoading(false);
} catch(error) {
setError(error);
}
};
useEffect(() => {
fetchData();
}, []); //From what the documentation says, this [] should stop it from running more than once.
return [ data, error, isLoading ];
};
EDIT: I ran the chrome developer tools with the react extension and saw something that might be useful:
Image showing Component2 (Spellbook) run inside itself infinite times
You can modify your custom hook useHandbook's useEffect hook to have URL as dependency, since useEffect is similar to componentWillMount, componentDidUpdate and componentWillUnmount, in your case it is componentDidUpdate multiple times. So what you can do is.
useEffect(() => {
fetchData();
}, [url]);
Since there is no need to fetch data agin unless URL is changed
I found the mistake. Component2's real name was Spellbook which I had also called the rendering component that I had not yet made. It turns out I was calling the component from within itself.
Easy to see in the image at the edit part of the question.