Developer Diaries – Alert Blocks, Email for File, a Gutenberg Sidebar, and Block Colors Idea

person piling blocks
Photo by La-Rel Easter on Unsplash

It’s been a bit since my last developer diary. The last time I talked about eating dog food. Yuck! Feel free to read the rest of the posts in the Developer Diaries topic.

Table of Contents

Some Quick Updates

WP Notices

After a strong opening, I’ve had a bit of a slump on WPNotices. Rest-assured, I will keep at it. Just nothing in WordPress annoys me lately, but that can change in a heart-beat. Ben W. has pledged a few articles, so I’m curious about what he can come up with. And since I’m pro-diversity, I encourage anyone, and I mean anyone, to write in. If English isn’t your first language, I can help you edit it. I’m very patient 🙂

Custom Query Blocks

Custom Query Blocks crossed the 900+ install count. It’s amazing that people are using it. Somebody this week asked me if they could fork the plugin. I was like, sure, it’s GPL, and you can do with it as you please. I did ask what they wanted to fork it for, but I have yet to receive a response.

Custom Query Blocks recently released an update that allows users to map pages to a 404 error, so the 404 error page can be completely customized using your favorite page-builder or Gutenberg layout.

I demonstrate how to create a 404 page with GeneratePress in this article.

As always, you can get the plugin here:

Simple Comment Editing

Every Sunday WordPress.org updates its stats of install growth and decline. I was a bit dismayed to see my baby, Simple Comment Editing, to go below 3,000+ installs, where it has sat for a file. I had even hoped that I’d be closer to 4,000 by now, but alas, it is not to be.

I don’t believe comments in WordPress are dead, but they are declining in popularity, even amongst bloggers. I do, for the most part, try to leave comment sections available throughout my site even though sometimes it’s a pain to manage the occasional spam comment or something that got past Akismet.

It’s just a little bit depressing. I have spent a lot of time on Simple Comment Editing. To see it and comments declining in popularity and more and more blogs switching off comments, I feel the reader experience is a one-way street without any effective method of feedback aside from social media, which is not owned by the reader.

I wish more bloggers would switch their comments back on. Akismet is a great and mostly free service and nowadays, comment spam is pretty much a thing of the past. Then you can spruce up your comment section with a variety of comment solutions such as social logins, subscribing to newsletter lists, becoming site subscribers, etc. Oh yeah, and comment editing and community building.

Event Tracking for Gravity Forms

I hold the semi but non-official documentation to the Event Tracking for Gravity Forms WordPress plugin.

It sits on an amazing user-base and 33 pure five-star ratings. This plugin is technically not mine anymore, however, people still ask me for help on it.

I’d like to possibly approach the new owners and see if there is any benefit of bringing new life to it and helping build core plugin sales.

User Profile Picture

User Profile Picture, again, technically not mine, is in bad need of an update. This is sorta/kinda semi-mostly on me. I’ve been given permission to keep the plugin up-to-date, but so far I haven’t felt incentivized enough or given a future plan to optimistic about the plugin. I suppose it’s on me to see if I can use my brain and see if there’s still any type of money to be made from this type of plugin.

Social Menu Icons

For some reason, Social Menu Icons continues to grow. It now has over 600 installs. It perplexes me 🙂

Highlight and Share

Highlight and Share is another plugin that I believe deserves more attention. You can see it in action on this site when you highlight text. You can even do inline text and even have a custom block like below.

This is a custom Highlight and Share block where you can share content with just a click.
Click to Share share

I personally think Highlight and Share is an underrated content marketing plugin, and it’s dead simple to use, so it’s kinda like, why not use it? Anyways, I’m not giving up on Highlight and Share. However, it’s now more of a hobby plugin than something I could potentially make money off of.

Move On With It

Okay, enough of the quick updates. Here’s the meat of the content.

Alert Blocks

I forked Andrew Lima’s Simple Alert Blocks earlier last week in the hopes of making it into a more powerful and option-filled Gutenberg alert block solution. I’ve tested other notices, and none came close to what I wanted. I wanted 100% flexibility and a killer icon picker.

GenerateBlocks has some killer components, so I forked several of their components. Thanks, people behind GeneratePress. You helped me learn a lot about Gutenberg.

I wrote a post with a link to the download the plugin on my alert notices release post.

Email for File

I have been secretly working on my next project, Email for File. It’s a secretive project to me, but I did demonstrate some of the functionality to my co-workers. After some feedback, it was apparent that I needed a wider audience, so I’m reworking some of the architecture.

I’m also debating how much should be extended to other developers. I do not plan on releasing Email for File on .org and I plan on having a reasonable licensing plan.

One thing my co-workers recommended is that it can be used as an email capture tool with single-opt-in. This means we can add them to a MailChimp list if another opt-in is selected for double-opt-in. This opens up the possibilities for newsletter building by integrating with other newsletter services. This could be interesting.

Here’s the current UI for the plugin sidebar:

Email for File Options Screen in Gutenberg Sidebar
Email for File Options Screen in Gutenberg Sidebar

After you enable email protection, the page/post/content item will be locked down for only certain email addresses. If you are using it to collect emails, then any email address can be accepted. If you would like to restrict the page to a single email address, you can do that too. Likewise for TLDs if you are on some type of Intranet or all hold the same type of email address (i.e., a business email address).

Here’s the lock screen, which I plan to make themeable:

Email for File Lock Screen
Email for File Lock Screen

Gutenberg Sidebars

Coding Gutenberg Sidebars is tough. For me, it was tough to do a sidebar because a sidebar the way most people write them does not have any state or default prop values. This means the sidebar is rather limited as it’s typically a default state and then you have to marry the rest and hope it works.

For my case, it took a few steps.

Adding the Post Meta

Here’s the example PHP class where I register my meta boxes. Since I don’t care about non-Gutenberg meta boxes, I kept mine simple. The key here is that they show_in_rest and that the fields are underlined since they are technically custom fields.

<?php /** * Add meta boxes to post types. * * @package email-for-file */ namespace Email_File_MR\Includes; /** * Meta Box class */ class Meta_Boxes { /** * Class runner. * * @since 1.0.0 */ public function run() { add_action( 'init', array( $this, 'register_meta_boxes' ) ); } ... }
/** * Register Meta Boxes. */ public function register_meta_boxes() { /** * Meta for enabling/disabling email protection. */ register_post_meta( '', '_effmr_enable_email_control', array( 'show_in_rest' => true, 'type' => 'boolean', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Meta for mode. Values are email_only, email_multiple, email_tld, email_collection. */ register_post_meta( '', '_effmr_mode', array( 'show_in_rest' => true, 'type' => 'string', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); ... }

Sidebar Initialization

In my main Gutenberg JavaScript entry-point, I have this in my src folder:

import EFFPluginSidebar from "./EFFPluginSidebar"; const { __ } = wp.i18n; // Import __() from wp.i18n const { registerPlugin } = wp.plugins; const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost; const { Fragment } = wp.element; registerPlugin("email-for-file-mr-enable-email-protection", { icon: "email-alt2", render: () => { return ( <Fragment> <PluginSidebarMoreMenuItem target="eff-off-sidebar"> {__("Email for File", "email-for-file-mr")} </PluginSidebarMoreMenuItem> <PluginSidebar name="eff-off-sidebar" title={__("Email for File", "email-for-file-mr")} > <EFFPluginSidebar /> </PluginSidebar> </Fragment> ); }, });

In the above code I:

  • Pull in my sidebar, which I’ll use as a component.
  • Pull in my WP dependencies.
  • Register the plugin, in which I used slug email-for-file-mr-enable-email-protection.
  • Gave the plugin a dashicon of email-alert2.
  • Built the plugin sidebar using JSX.
  • Included a reference to my sidebar component using: <EFFPluginSidebar />

Of course, everything is dependent on your build script. As soon as I perfect mine, I will share it.

Inside EFFPluginSidebar, the real magic brews.

I can set up my dependencies:

const { Component, Fragment } = wp.element; const { __ } = wp.i18n; const { PanelBody, TextControl, SelectControl, ToggleControl, RangeControl, } = wp.components; const { withDispatch } = wp.data;

I can initialize class and state with defaults:

class EFFPluginSidebar extends Component { constructor() { super(...arguments); this.state = { enableEmailControl: false, singleEmailAddress: "", selfDestructEnabled: false, selfDestructImmediately: false, selfDestructViews: 5, lockScreenTheme: "default", }; }

Set my component-did-mount to get the current meta values of meta keys when the edit screen loads:

componentDidMount = () => { const { _effmr_enable_email_control, _effmr_single_email_address, _effmr_self_destruct_enabled, _effmr_self_destruct_immediately, _effmr_self_destruct_views, _effmr_lock_screen_theme, } = wp.data.select("core/editor").getEditedPostAttribute("meta"); if ( _effmr_enable_email_control != null && _effmr_enable_email_control.length != 0 ) { this.setState({ enableEmailControl: _effmr_enable_email_control, }); } if ( _effmr_single_email_address != null && _effmr_single_email_address.length != 0 ) { this.setState({ singleEmailAddress: _effmr_single_email_address, }); } if ( _effmr_self_destruct_enabled != null && _effmr_self_destruct_enabled.length != 0 ) { this.setState({ selfDestructEnabled: _effmr_self_destruct_enabled, }); } if ( _effmr_self_destruct_immediately != null && _effmr_self_destruct_immediately.length != 0 ) { this.setState({ selfDestructImmediately: _effmr_self_destruct_immediately, }); } if ( _effmr_self_destruct_views != null && _effmr_self_destruct_views.length != 0 ) { this.setState({ selfDestructViews: _effmr_self_destruct_views[0], }); } if ( _effmr_lock_screen_theme != undefined && _effmr_lock_screen_theme.length != 0 ) { this.setState({ lockScreenTheme: _effmr_lock_screen_theme, }); } };

I render out my interface:

render() { // Get themes from localized var. let themesSelect = []; const themes = Object.entries(email_for_file_mr.themes); for (const key of themes) { themesSelect.push( { value: key[0], label: key[1], } ); } return ( <Fragment> <PanelBody title={__("Emails", "email-for-file-mr")}> <ToggleControl label={__("Email Protection", "email-for-file-mr")} checked={this.state.enableEmailControl} onChange={(value) => { this.setState({ enableEmailControl: value, }); this.props.setMetaFieldValue( "_effmr_enable_email_control", value ); }} /> ...

And finally to match all the meta up, you export the file:

export default withDispatch((dispatch) => { return { setMetaFieldValue: function (key, value) { dispatch("core/editor").editPost({ meta: { [key]: value } }); }, }; })(EFFPluginSidebar);
import EFFPluginSidebar from "./EFFPluginSidebar"; const { __ } = wp.i18n; // Import __() from wp.i18n const { registerPlugin } = wp.plugins; const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost; const { Fragment } = wp.element; registerPlugin("email-for-file-mr-enable-email-protection", { icon: "email-alt2", render: () => { return ( <Fragment> <PluginSidebarMoreMenuItem target="eff-off-sidebar"> {__("Email for File", "email-for-file-mr")} </PluginSidebarMoreMenuItem> <PluginSidebar name="eff-off-sidebar" title={__("Email for File", "email-for-file-mr")} > <EFFPluginSidebar /> </PluginSidebar> </Fragment> ); }, });
const { Component, Fragment } = wp.element; const { __ } = wp.i18n; const { PanelBody, TextControl, SelectControl, ToggleControl, RangeControl, } = wp.components; const { withDispatch } = wp.data; class EFFPluginSidebar extends Component { constructor() { super(...arguments); this.state = { enableEmailControl: false, singleEmailAddress: "", selfDestructEnabled: false, selfDestructImmediately: false, selfDestructViews: 5, lockScreenTheme: "default", }; } componentDidMount = () => { const { _effmr_enable_email_control, _effmr_single_email_address, _effmr_self_destruct_enabled, _effmr_self_destruct_immediately, _effmr_self_destruct_views, _effmr_lock_screen_theme, } = wp.data.select("core/editor").getEditedPostAttribute("meta"); if ( _effmr_enable_email_control != null && _effmr_enable_email_control.length != 0 ) { this.setState({ enableEmailControl: _effmr_enable_email_control, }); } if ( _effmr_single_email_address != null && _effmr_single_email_address.length != 0 ) { this.setState({ singleEmailAddress: _effmr_single_email_address, }); } if ( _effmr_self_destruct_enabled != null && _effmr_self_destruct_enabled.length != 0 ) { this.setState({ selfDestructEnabled: _effmr_self_destruct_enabled, }); } if ( _effmr_self_destruct_immediately != null && _effmr_self_destruct_immediately.length != 0 ) { this.setState({ selfDestructImmediately: _effmr_self_destruct_immediately, }); } if ( _effmr_self_destruct_views != null && _effmr_self_destruct_views.length != 0 ) { this.setState({ selfDestructViews: _effmr_self_destruct_views[0], }); } if ( _effmr_lock_screen_theme != undefined && _effmr_lock_screen_theme.length != 0 ) { this.setState({ lockScreenTheme: _effmr_lock_screen_theme, }); } }; render() { // Get themes from localized var. let themesSelect = []; const themes = Object.entries(email_for_file_mr.themes); for (const key of themes) { themesSelect.push( { value: key[0], label: key[1], } ); } return ( <Fragment> <PanelBody title={__("Emails", "email-for-file-mr")}> <ToggleControl label={__("Email Protection", "email-for-file-mr")} checked={this.state.enableEmailControl} onChange={(value) => { this.setState({ enableEmailControl: value, }); this.props.setMetaFieldValue( "_effmr_enable_email_control", value ); }} /> {this.state.enableEmailControl && ( <Fragment> <TextControl label={__("Enter an Email Address", "email-for-file-mr")} placeholder={"domain@domain.com"} value={this.state.singleEmailAddress} onChange={(value) => { this.setState({ singleEmailAddress: value, }); this.props.setMetaFieldValue( "_effmr_single_email_address", value ); }} /> <SelectControl label={__("Select a Lock Screen Theme", "email-for-file-mr")} value={this.state.lockScreenTheme} options={themesSelect} onChange={(value) => { this.setState({ lockScreenTheme: value, }); this.props.setMetaFieldValue( "_effmr_lock_screen_theme", value ); }} /> <ToggleControl label={__("Self-destruct Mode", "email-for-file-mr")} checked={this.state.selfDestructEnabled} onChange={(value) => { this.setState({ selfDestructEnabled: value, }); this.props.setMetaFieldValue( "_effmr_self_destruct_enabled", value ); }} help={__("Self-destruct mode simply changes the post from Public to Private so authors, editors, and admins can still view it.", "email-for-file-mr")} /> {this.state.selfDestructEnabled && ( <Fragment> <ToggleControl label={__("Self-destruct Immediately", "email-for-file-mr")} checked={this.state.selfDestructImmediately} onChange={(value) => { this.setState({ selfDestructImmediately: value, }); this.props.setMetaFieldValue( "_effmr_self_destruct_immediately", value ); }} help={__("Once the person with the email views the content, the post will immediately change to Private.", "email-for-file-mr")} /> {!this.state.selfDestructImmediately && ( <Fragment> <RangeControl label={__( "Self Destruct After x Many Views", "email-for-file-mr" )} value={this.state.selfDestructViews} onChange={(value) => { this.setState({ selfDestructViews: value, }); this.props.setMetaFieldValue( "_effmr_self_destruct_views", value ); }} min={1} max={10} step={1} help={__("A person with the email address can view the content x number of times before the post gets changed from Public to Private.", "email-for-file-mr")} /> </Fragment> )} </Fragment> )} </Fragment> )} </PanelBody> </Fragment> ); } } export default withDispatch((dispatch) => { return { setMetaFieldValue: function (key, value) { dispatch("core/editor").editPost({ meta: { [key]: value } }); }, }; })(EFFPluginSidebar);
<?php /** * Add meta boxes to post types. * * @package email-for-file */ namespace Email_File_MR\Includes; /** * Meta Box class */ class Meta_Boxes { /** * Class runner. * * @since 1.0.0 */ public function run() { add_action( 'init', array( $this, 'register_meta_boxes' ) ); } /** * Register Meta Boxes. */ public function register_meta_boxes() { /** * Meta for enabling/disabling email protection. */ register_post_meta( '', '_effmr_enable_email_control', array( 'show_in_rest' => true, 'type' => 'boolean', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Meta for mode. Values are email_only, email_multiple, email_tld, email_collection. */ register_post_meta( '', '_effmr_mode', array( 'show_in_rest' => true, 'type' => 'string', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Meta for mode email_only. Shows a single email address. */ register_post_meta( '', '_effmr_single_email_address', array( 'show_in_rest' => true, 'type' => 'string', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Meta for mode email_multiple. Stores multiple email addresses. */ register_post_meta( '', '_effmr_multiple_email_addresses', array( 'show_in_rest' => true, 'type' => 'string', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Meta for mode email_tld. Enter a domain tld. */ register_post_meta( '', '_effmr_tld_email_addresses', array( 'show_in_rest' => true, 'type' => 'string', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Whether the post will be published to draft after certain conditions are met. */ register_post_meta( '', '_effmr_self_destruct_enabled', array( 'show_in_rest' => true, 'type' => 'boolean', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Self-destruct after first access. */ register_post_meta( '', '_effmr_self_destruct_immediately', array( 'show_in_rest' => true, 'type' => 'boolean', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Self-destruct after a date option. */ register_post_meta( '', '_effmr_self_destruct_expire_schedule', array( 'show_in_rest' => true, 'type' => 'boolean', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * If on a schedule, whether to show a countdown timer on the lock screen. */ register_post_meta( '', '_effmr_self_destruct_show_countdown', array( 'show_in_rest' => true, 'type' => 'boolean', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Self-destruct after so many views. */ register_post_meta( '', '_effmr_self_destruct_views', array( 'show_in_rest' => true, 'type' => 'integer', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Date to self-destruct. */ register_post_meta( '', '_effmr_self_destruct_date', array( 'show_in_rest' => true, 'type' => 'integer', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Lock screen selected. */ register_post_meta( '', '_effmr_lock_screen_theme', array( 'show_in_rest' => true, 'type' => 'string', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * Whether mailchimp collection will be shown after code is entered. */ register_post_meta( '', '_effmr_mailchimp_list_enabled', array( 'show_in_rest' => true, 'type' => 'boolean', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); /** * MailChimp list for the post. */ register_post_meta( '', '_effmr_mailchimp_list', array( 'show_in_rest' => true, 'type' => 'string', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); } }

Dependencies when enqueuing:

array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor', 'wp-plugins', 'wp-edit-post', 'wp-data' ),

Block Editor Colors

Finally, I want to talk about Block Editor Colors. This type of thing should be in core, but I understand why it’s not.

So far the plugin above has 600+ installs and is by MotoPress and Gutenberg Block Editor Colors.

The above plugin allows you to create custom color schemes for Gutenberg color palettes. The way they do this is creative and useful, but I want it to do more, and I’m hoping something like this will make it more popular than a 600+ install base.

We need a full-on block color and gradient editor. This will allow us to create color schemes, gradient schemes, and add to, or overwrite base colors. In addition, it would be nice if the gradients could be added to the color palettes, that way any color field could be a gradient field. This may require taking over the color picker control with something a bit more custom.

Furthermore, I’d love to see import/export capabilities. This will allow others to suggest color schemes and have those downloadable/importable.

Conclusion

I droned on on this one. It was fun though 🙂 – Cheers.

Ronald Huereca

Ronald Huereca
Ronald Huereca

Ronald Huereca

Ronald has been part of the WordPress community since 2006, starting off writing and eventually diving into WordPress plugin development and writing tutorials and opinionated pieces.

No stranger to controversy and opinionated takes on tough topics, Ronald writes honestly when he covers a topic.

Leave a Comment