How do I save global inspector controls in Gutenberg editor? - javascript

I'm new to WP's gutenberg and React(but not WP/PHP). I'm trying to add a series of custom controls that show up on all core blocks. Using the WP documentation, I was able to add a new inspector section with a simple toggle:
var el = wp.element.createElement;
const { ToggleControl, PanelBody, PanelHeader, BaseControl } = wp.components;
var withInspectorControls = wp.compose.createHigherOrderComponent( function( BlockEdit ) {
return function( props ) {
return el(
wp.element.Fragment,
{},
el(
BlockEdit,
props
),
el(
wp.editor.InspectorControls,
{},
el(
PanelBody,
{
title: 'Section Controls'
},
el(
ToggleControl,
{
label: 'Full Width Section',
checked: props.attributes.full_width_section,
onChange: function(value){ props.setAttributes( { full_width_section: value } ); }
}
),
)
)
);
};
}, 'withInspectorControls' );
wp.hooks.addFilter( 'editor.BlockEdit', 'brink/with-inspector-controls', withInspectorControls );
What I can't do is figure out the proper way to utilize blocks.getSaveContent.extraProps to save the new full_width_section toggle.
I know I'll then need to figure out how to manipulate the block output after this, but one problem at a time!

I finally figured this out by dissecting a few Gutenberg plugins. In this case, before adding controls to the inspector I had to create attributes for all blocks:
// Attributes
const backgroundSettings = {
fullWidthSection: {
type: "boolean",
},
};
function addAttributes(settings) {
const { assign } = lodash;
settings.attributes = assign(settings.attributes, backgroundSettings);
return settings;
}
wp.hooks.addFilter( 'blocks.registerBlockType', 'brink/add-attributes', addAttributes ); // Add Attributes
From here all you have to do is add any attributes you want and their settings, default, etc. in backgroundSettings.

Related

WordPress Gutenberg Blocks Get Author Info

I feel like what I want to accomplish is simple: create a custom WordPress Gutenberg Block that auto-populates with the author name and image and is not editable. Then render this block on the frontend post.
I've gone at this from a lot of different angles, and I think what I need is a Dynamic Block
https://developer.wordpress.org/block-editor/tutorials/block-tutorial/creating-dynamic-blocks/
First issue is the wp.data.select("core/editor").getCurrentPostId() doesn't load the post ID. But then I'll be honest, I don't know if the return would even have the author info I want anyway. Any guidance is most appreciated.
const post_id = wp.data.select("core/editor").getCurrentPostId();
var el = wp.element.createElement;
wp.blocks.registerBlockType('my-plugin/author-block', {
title: 'Author Footer',
icon: 'admin-users',
category: 'my-blocks',
attributes: {
// Nothing stored
},
edit: wp.data.withSelect( function(select){ // Get server stuff to pass to our block
return {
posts: select('core').getEntityRecords('postType', 'post', {per_page: 1, post_id})
};
}) (function(props) { // How our block renders in the editor
if ( ! props.posts ) {
return 'Loading...';
}
if ( props.posts.length === 0 ) {
return 'No posts';
}
var post = props.posts[0];
// Do stuff with the post like maybe get author name and image???
// ... Return elements to editor viewer
}), // End edit()
save: function(props) { // Save the block for rendering in frontend post
// ... Return saved elements
} // End save()
});
Finally figured something out so thought I'd share.
edit: function(props) { // How our block renders in the editor in edit mode
var postID = wp.data.select("core/editor").getCurrentPostId();
wp.apiFetch( { path: '/wp/v2/posts/'+postID } ).then( post => {
var authorID = post.author
wp.apiFetch( { path: '/wp/v2/users/'+authorID } ).then( author => {
authorName = author.name;
authorImage = author.avatar_urls['96'];
jQuery('.author-bottom').html('<img src="'+authorImage+'"><div><h6>Written by</h6><h6 class="author-name">'+authorName+'</h6></div>');
});
});
return el(
'div',
{
className: 'author-bottom'
},
el(
'img',
{
src: null
}
),
el(
'div',
null,
el(
'h6',
null,
'Written by'
),
el(
'h6',
{
className: 'author-name'
},
'Loading...'
)
)
); // End return
}, // End edit()
As soon as the block loads, it initiates a couple apiFetch calls (one to get the author ID, then another to get the author's name and image).
While that's happening, the author block loads with the temporary text "Loading..."
Once the apiFetch is complete I used jQuery to update the author block.
There is no save function because it's a dynamic block. I registered the block in my plugin php file like so:
function my_author_block_dynamic_render($attributes, $content) {
return '
<div class="author-bottom">
<img src="'.get_avatar_url(get_the_author_email(), ['size' => '200']).'">
<div>
<h6>Written by</h6>
<h6 class="author-name">'.get_the_author().'</h6>
</div>
</div>';
}
function my_dynamic_blocks() {
wp_register_script(
'my-blocks-script',
plugins_url( 'my-block.js', __FILE__ ),
array( 'wp-blocks', 'wp-element' ));
register_block_type( 'my-blocks/author-block', array(
'editor_script' => 'my-blocks-script',
'render_callback' => 'my_author_block_dynamic_render'));
}
add_action( 'init', 'my_dynamic_blocks' );
I couldn't find a good tutorial on creating a custom author block, so hopefully this example helps some poor soul down the road!

Java Script / Wordpress Gutenberg Block Plugin / cant add a second class to div

Maybe someone can help me with this strange behavior ...
I am writing on a "Wordpress Gutenberg Block" plugin and just want to give the first div a second class in the return function.
It works but not then it should.
I want get:
<div class="wp-block-womoblocks-katblock col-lg-4">
But i get always:
<div class="wp-block-womoblocks-katblock undefinedcol-lg-4">
Here is the code of the function:
save: function( props ) {
var attributes = props.attributes;
var colg4 = "col-lg-4";
var colclass = props.className + colg4;
return (
el( 'div', { className: colclass },
el( 'div', { className: 'col-lg-4' },
el( 'div', { className: 'kat-post' },
el( RichText.Content, {
tagName: 'h2', value: attributes.title
} ),
attributes.mediaURL &&
el( 'div', { className: 'kat-image' },
el( 'img', { src: attributes.mediaURL } ),
),
el( RichText.Content, {
tagName: 'div', className: 'kat-ausz', value: attributes.instructions
} ),
)))
);
},
I have tried several options but I always get this undefined before col-lg-4 and have no ideas on how to solve it.
Thank you very much!
The problem is in the assignment of colclass because there is no need for props.className.
In save function of registerBlockType class is added automatically to the outermost element but in edit function you have to explicitly write it so mistakenly you are using same approach on both.
I also suggest you to use the more latest ES6 / ESNext syntax instead of ES5 because it's friendly to code in that, Gutenberg docs also have a toggle that shows you ESNext code.

Insert custom element in CKEditor5

In CKEditor5 I am creating a plugin to insert a span element to show a tooltip. The idea is to show a tooltip with a (foot)note inside of it while the element itself will display an incremental number. In CKEditor4 I made something like this with:
CKEDITOR.dialog.add( 'footnoteDialog', function( editor ) {
return {
title: 'Footnote Properties',
minWidth: 400,
minHeight: 100,
contents: [
{
id: 'tab-basic',
label: 'Basic Settings',
elements: [
{
type: 'text',
id: 'content',
label: 'Content of footnote',
validate: CKEDITOR.dialog.validate.notEmpty( "Footnote field cannot be empty." )
}
]
}
],
onOk: function() {
var dialog = this;
var footnote = editor.document.createElement( 'span' );
footnote.setAttribute('class', 'footnote');
footnote.setAttribute('data-toggle', 'tooltip');
footnote.setAttribute( 'title', dialog.getValueOf( 'tab-basic', 'content' ) );
footnote.setText('[FN]');
editor.insertElement( footnote );
}
};
});
[FN] would be transformed in an incremental number.
Now I try to make this plugin with in CKEditor5, but with no success. There are two issues I run in to. Fist, I can't manage to insert the element inside the text. Second, when I want to use the attribute data-toggle this doesn't work because of the - syntax. This is my current code:
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import pilcrowIcon from '#ckeditor/ckeditor5-core/theme/icons/pilcrow.svg';
import ButtonView from '#ckeditor/ckeditor5-ui/src/button/buttonview';
export default class Footnote extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add( 'footnote', locale => {
const view = new ButtonView( locale );
view.set( {
label: 'Insert footnote',
icon: pilcrowIcon,
tooltip: true
} );
view.on( 'execute', () => {
const source = prompt( 'Footnote' );
editor.model.schema.register( 'span', { allowAttributes: ['class', 'data-toggle', 'title'] } );
editor.model.change( writer => {
const footnoteElement = writer.createElement( 'span', {
class: 'footnote',
// data-toggle: 'tooltip',
title: source
});
editor.model.insertContent( footnoteElement, editor.model.document.selection );
} );
} );
return view;
} );
}
}
How can I make sure my span element is inserted and also contains data-toggle="tooltip"?
For anyone who comes across this, there is a good description of how to set up inline elements in the model and view and then map between them here - How to add "target" attribute to `a` tag in ckeditor5?
Based on my experience, you will also need to set up Javascript code for a command that is run when a button is pressed. The command will insert the new information into the model, then this mapping code will convert it to the view (HTML) for display.

How to apply show less and show more on cells of a reactive table in meteor

document_table_Settings : function ()
{
return{
rowsPerPage: 5,
showNavigation: 'auto',
showColumnToggles: false,
fields: [
{key: 'para',label: 'Para',sortable: false},
{key: 'desc', label: 'Description',sortable: false},
{
key: 'rowId', label: 'Delete',sortable: false, fn: function (rowId, object) {
var html = "<button name='Del' id=" + rowId + " class='btn btn-danger'>Delete</button>"
return new Spacebars.SafeString(html);
}
},
{
key: 'rowId', label: 'Edit',sortable: false, fn: function (rowId, object) {
var html = "<button name='edit' id=" + rowId + " class='btn btn-warning'>Edit</button>"
return new Spacebars.SafeString(html);
}
}
]
};
}
I want to show description entries having show more and show less feature .As the description is long enough. so after 100 character it shows button to toggle.
If I understand you correctly, you are trying to only show the first 100 characters of the 'Description' column in the Reactive Table and then add some mechanism so that the user can click or rollover to see the entire 'Description' text.
There are a few ways to achieve this and I have provided two options below (in order of simplicity).
For a low tech rollover option, truncate the text to only show the first 100 characters, add an ellipsis (...) to the end of your text, then use the title property in a span element to show the full text on rollover.
First you will need to define a 'truncate' Template helper (I would make this a global helper so that you can use anywhere in your app).
Template.registerHelper('truncate', function(strValue, length) {
var len = DEFAULT_TRUNCATE_LENGTH;
var truncatedString = strValue;
if (length && length instanceof Number) {
len = length;
}
if (strValue.length > len) {
truncatedString = strValue.substr(1, len) + "...";
}
return truncatedString;
});
Then create a new Template for the column.
<template name="field_description">
<span title="{{data.description}}">{{truncate data.description}}</span>
</template>
And finally, change your Reactive Table configuration to use a Template.
fields: [
...,
{ key: 'desc', label: 'Description', tmpl: Template.field_description }
...,
];
For a slightly more complicated option, you can take a similar approach but add a clickable link that would show more or less detail. To get it to work you have to define a few Reactive Vars, define an event handler, and change your 'Description' Template accordingly. Here is a rough solution that should work.
Change your template like so.
<template name="field_description">
<span>{{truncatedDescription}}
{{#if showLink}}
{{linkState}}
{{/if}}
</span>
</template>
Then add the necessary logic to your field_description template (including an event handler).
import { Template } from 'meteor/templating';
import './field-description.html';
Template.field_descirption.onCreated(function() {
const MAX_LENGTH = 100;
this.description = new ReactiveVar(Template.currentData().description);
this.showMore = new ReactiveVar(true);
if (this.description.get().length > MAX_LENGTH) {
this.description.set(Template.currentData().description.substr(1, MAX_LENGTH));
}
this.showLink = () => {
return Template.currentData().description.length > MAX_LENGTH;
};
this.toggleTruncate = () => {
if (this.showMore.get()) {
this.description.set(Template.currentData().description);
this.showMore.set(false);
} else {
this.description.set(Template.currentData().description.substr(1, MAX_LENGTH));
this.showMore.set(true);
}
};
});
Template.field_descirption.helpers({
truncatedDescription: function() {
return Template.instance().description.get();
},
showLink: function() {
return Template.instance().showLink();
},
linkState: function() {
if (Template.instance().showMore.get()) {
return 'show more';
} else {
return 'show less';
}
};
});
Template.field_descirption.events({
'click .js-more-less'(event, instance) {
instance.toggleTruncate();
},
});
Lastly, make sure your Reactive Table config is still setup to use a Template for the field.
fields: [
...,
{ key: 'desc', label: 'Description', tmpl: Template.field_description }
...,
];
Note that the second option makes use of Meteor's Reactivity to solve the problem. Let me know if you need additional explanation on how the 2nd solution works.
That should do it!

CKEditor discards widget data after switching to source mode

I am developing a simple widget using the following code. Can anyone please explain to me why the attribute and text iv set turn to undefined after switching to "source mode"?
<pre>
/*
/dialogs/b2eupobject.js
*/
CKEDITOR.dialog.add( 'b2eupobject', function( editor ) {
return {
title: 'Edit Simple Box',
minWidth: 200,
minHeight: 100,
contents: [
{
id: 'info',
elements: [
{
id: 'objectId',
type: 'select',
label: 'ObjectName',
items: [ ['select', '-1' ],
['first name', '1' ],
['last name', '2' ],
],
setup: function( widget ) {
this.setValue( widget.data.title );
//this.setValue(widget.data.desc);
},
commit: function( widget ) {
widget.setData( 'title', this.getValue() );
var input = this.getInputElement().$;
widget.setData('desc',input.options[ input.selectedIndex ].text);
}
},
]
}
],
onShow: function() {
// The code that will be executed when a dialog window is loaded. This function will be defined above the onOk function
var selection = editor.getSelection(); //get to the element that is selected by the user (either highlighted or just having the caret inside)
var element = selection.getStartElement(); // get the element in which the selection starts
if ( element )
element = element.getAscendant( 'em', true ); // get the abbr element
console.log(this);
},
onOk: function() {
// The code that will be executed when a dialog window is loaded. This function will be defined above the onOk function
var selection = editor.getSelection(); //get to the element that is selected by the user (either highlighted or just having the caret inside)
var element = selection.getStartElement(); // get the element in which the selection starts
// if ( element )
console.log (element);
console.log(this);
}
};
} );
</pre>
<pre>
/*
plugin.js
*/
CKEDITOR.plugins.add( 'b2eupobject', {
requires: 'widget', //reference the generic Widget plugin that provides the Widget API
icons: 'b2eupobject',
init: function( editor ) {
CKEDITOR.dialog.add( 'b2eupobject', this.path + 'dialogs/b2eupobject.js' );
editor.widgets.add( 'b2eupobject', {
button: 'Insert user object',
template:'<em class="b2eUp">test </em>',
toolbar:'widgets',
parts: {elm: 'em.b2eUp'}, //this is a CSS selector of the element within the template above that you want to target
allowedContent: 'em ( b2eUp) [title]',
requiredContent : 'em(b2eUp) [title]',
dialog: 'b2eupobject',
init: function (widget) {
console.log("widget init : "+widget);
// widget.parts.elm.setAttribute('title',this.data.title);
// widget.parts.elm.setHtml(this.data.desc);
},
data: function (widget) {
this.parts.elm.setAttribute('title',this.data.title);
this.parts.elm.setHtml(this.data.desc);
},
upcast: function( element ) {
alert(element.name == 'em' && element.hasClass( 'b2eUp' ));
return element.name == 'em' && element.hasClass( 'b2eUp' );
},
} );
var pluginDirectory = this.path;
editor.addContentsCss( pluginDirectory + 'style/style.css' );
if ( editor.contextMenu ) {// check if contextmenu plugin exsists
editor.addMenuGroup( 'b2eupgrouGroup' ); // register a new menu group called abbrGroup.
editor.addMenuItem( 'b2eupItem', { //register a new menu item that will belong to the newly created group.
label: 'Edit user object',
icon: this.path + 'icons/b2eupobject.png',
command: 'b2eupobject',
group: 'b2eupgrouGroup'
});
editor.contextMenu.addListener( function( element ) { // add an event listener function that will be called whenever the context menu is fired.
if ( element.getAscendant( 'em', true ) ) {
// check if element is of type abbr
return { simpleboxitem: CKEDITOR.TRISTATE_OFF };
}
});
};
},
enter code here
} );
</pre>
I'm using CKEditor version 4.5 and I have both the widget and dialog plugins installed.
******* addition ******
when i return from source mode in the editor i can see that the upcast function is invoked.
writing to log, i can see that it returns true and that the element object :
FF console:
attributes: Object { class="b2eUp", title="1", data-cke-widget-data="%7B%7D", more...}
children:[Object { value="first name", _={...}, parent={...}, more...}]

Categories