Half-Elf on Tech

Thoughts From a Professional Lesbian

Category: How To

  • Show Feedback in “Right Now”

    Show Feedback in “Right Now”

    The “Right Now” section of the WordPress dashboard is a great way to get an overview of the goings on of your site. But it doesn’t quite list everything. What if you could add things like ‘messages’ to the the “At a Glance” section like this:

    The "At a Glance" section, with messages added in.

    Guess what? You can!

    The Code

    Presmuing you’re using Jetpack’s contact form module, you automatically get a new kind of post called ‘Feedback.’ In order to make it display it’s count in “At a Glance,” there are two parts. First we add the CSS, which does the styling. Then we add the PHP that counts and displays the number of posts in feedback.

    add_action( 'dashboard_glance_items', 'helf_dashboard_glance' );
    add_action( 'admin_head', 'helf_dashboard_glance_css' );
    
    /*
     * Show Feedback in "Right Now"
     */
    function helf_dashboard_glance() {
    	if ( class_exists( 'Jetpack' ) && Jetpack::is_module_active( 'contact-form' ) ) {
    		foreach ( array( 'feedback' ) as $post_type ) {
    			$num_posts   = wp_count_posts( $post_type );
    			$count_posts = ( isset( $num_posts->publish ) ) ? $num_posts->publish : '0';
    			if ( 0 !== $count_posts ) {
    				if ( 'feedback' === $post_type ) {
    					// translators: %s is the number of messages
    					$text = _n( '%s Message', '%s Messages', $count_posts );
    				}
    				$text = sprintf( $text, number_format_i18n( $count_posts ) );
    				printf( '<li class="%1$s-count"><a href="edit.php?post_type=%1$s">%2$s</a></li>', esc_attr( $post_type ), wp_kses_post( $text ) );
    			}
    		}
    	}
    }
    
    /*
     * Custom Icon for Feedback in "Right Now"
     */
    function helf_dashboard_glance_css() {
    	if ( class_exists( 'Jetpack' ) && Jetpack::is_module_active( 'contact-form' ) ) {
    		?>
    		<style type='text/css'>
    			#adminmenu #menu-posts-feedback div.wp-menu-image:before, #dashboard_right_now li.feedback-count a:before {
    				content: '\f466';
    				margin-left: -1px;
    			}
    		</style>
    		<?php
    	}
    }
  • Selective DeGutenberging

    Selective DeGutenberging

    Okay let’s be honest, friends. Not everything is ready for Gutenberg. In fact, I myself have certain sites that aren’t ready for it today. That’s why there exists an excellent plugin known as the Classic Editor. This allows you to decide it you want to block Gutenberg (which is the default option) or if you want to allow users to chose between Gutenberg and Classic editors.

    The settings page for the Classic editor.

    But what if you want to go a step further?

    What if you want to have Gutenberg on by default for posts and pages, but not for a custom post type?

    Don’t worry. We can do that.

    The Code

    The basic idea is that if WP is 5.0 or higher, make sure that the Classic Editor is installed. If it’s less than 5.0, make sure Gutenberg is installed. Then block the code as needed:

    class HELF_Gutenberg {
    
    	public $gutenfree = array();
    
    	public function __construct() {
    		$this->gutenfree = array( 'post_type_ONE', 'post_type_TWO', 'post_type_THREE' );
    		add_action( 'current_screen', array( $this, 'gutenberg_removal' ) );
    	}
    
    	public function gutenberg_removal() {
    
    		// WP 5.0+ requires Classic Editor
    		// WP 4.9- requires Gutenberg
    		if ( ( version_compare( get_bloginfo( 'version' ), 5.0, '<=' ) && ! is_plugin_active( 'gutenberg/gutenberg.php' ) ) || ( version_compare( get_bloginfo( 'version' ), 5.0, '=>' ) && ! is_plugin_active( 'classic-editor/classic-editor.php' ) ) ) {
    			return;
    		}
    
    		// Intercept Post Type
    		$current_screen    = get_current_screen();
    		$current_post_type = $current_screen->post_type;
    
    		// If this is one of our custom post types, we don't gutenize
    		if ( in_array( $current_post_type, $this->gutenfree, true ) ) {
    			remove_filter( 'replace_editor', 'gutenberg_init' );
    			remove_action( 'load-post.php', 'gutenberg_intercept_edit_post' );
    			remove_action( 'load-post-new.php', 'gutenberg_intercept_post_new' );
    			remove_action( 'admin_init', 'gutenberg_add_edit_link_filters' );
    			remove_filter( 'admin_url', 'gutenberg_modify_add_new_button_url' );
    			remove_action( 'admin_print_scripts-edit.php', 'gutenberg_replace_default_add_new_button' );
    			remove_action( 'admin_enqueue_scripts', 'gutenberg_editor_scripts_and_styles' );
    			remove_filter( 'screen_options_show_screen', '__return_false' );
    		}
    	}
    
    }
    
    new HELF_Gutenberg();
  • Gutenberg Spoiler Blocks Redux

    Gutenberg Spoiler Blocks Redux

    While I did write up Spoiler Blocks for Gutenberg one way, I also sat down recently and rewrote it a little cleaner. The primary difference here is how I’m properly using defaults:

    The Code

    ( function( blocks, element, editor, components ) {
    
    	const { registerBlockType } = blocks;
    	const { RichText } = editor;
    	const { createElement } = element;
    	const { InspectorControls } = editor;
    	const { SelectControl, ToggleControl } = components;
    
    	registerBlockType( 'library/spoilers', {
    		title: __( 'Spoiler Warning' ),
    		icon: 'vault',
    		category: 'halfelf',
    		customClassName: false,
    		className: false,
    		attributes: {
    			content: {
    				source: 'children',
    				selector: 'div',
    				default: 'Warning: This post contains spoilers!'
    			}
    		},
    
    		save: function( props ) {
    			const content = props.attributes.content;
    			const container = createElement(
    				'div', { className: 'alert alert-danger' },
    				React.createElement( RichText.Content, { value: content })
    			);
    			return container;
    		},
    
    		edit: function( props ) {
    			const content = props.attributes.content;
    			const focus = props.focus;
    
    			function onChangeSpoiler( newContent ) {
    				props.setAttributes( { content: newContent } );
    			}
    
    			const editSpoiler = createElement(
    				RichText,
    				{
    					tagName: 'div',
    					className: props.className,
    					onChange: onChangeSpoiler,
    					value: content,
    					focus: focus,
    					onFocus: props.setFocus,
    				}
    			);
    
    			return createElement(
    				'div', { className: 'alert alert-danger' },
    				editSpoiler
    			);
    		},
    
    	});
    
    })(
    	window.wp.blocks,
    	window.wp.element,
    	window.wp.editor,
    	window.wp.components,
    	window.wp.i18n
    );
    

    Learning more and more about Gutenberg, and javascript, I’ve been able to iterate on the existing code and keep improving.

  • Gutenberg Categories

    Gutenberg Categories

    No, this isn’t something snazzy about doing cool things with categories within Gutenberg (although you could). This is about making a section for your own blocks. A category.

    What’s a Category?

    I’m incredibly lazy, so when I use blocks, I type /head and make sure I get the right block, hit enter, and keep on typing.

    The slash command at work!

    But. Sometimes I think I need a specific block and I don’t know the name, so I click on the circle + sign and I get a list of categories!

    Block categories

    You won’t see reusable if you didn’t make some, but the point is that you could make a custom category for your custom blocks.

    Show me the code!

    Glad you asked! You’re gonna love this one.

    add_filter( 'block_categories', function( $categories, $post ) {
    	return array_merge(
    		$categories,
    		array(
    			array(
    				'slug'  => 'halfelf',
    				'title' => 'Half-Elf',
    			),
    		)
    	);
    }, 10, 2 );

    Yep. That’s it! It’s an array, so you can add more and more and more as you want. Don’t go too wild, though, or you’ll make it too long.

  • Advanced Block Behaviour

    Advanced Block Behaviour

    Making a straight forward block to insert content in Gutenberg is (relatively) easy. I hate using the word ‘easy’ here, because in order to do it you will need to master a whole new world of coding, but all things considered, yes. It is easy. In fact, Gary (aka Pento) wrote a sample plugin that will let you convert a shortcode to a Gutenberg Block.

    I myself have written a mildly complicated Spoiler Block (there’s a new version of it I will share soon). But then the day happened that my cohort in lesbian TV crime, Tracy, expressed annoyance at how Gutenberg destroyed her listicle.

    What is a Listicle?

    Have you seen those posts that show you the top ten ways to LifeHack your used egg cartons? Those are listicles. They’re lists (list) articles (icle). Makes sense, right? Listicles have a pretty basic format as well, there will be a header with some sort of numerical mark, then content with images etc. This repeats for the number of times you have an article.

    Most people make them by hand by manually editing the numbers in their header. That’s all well and good until you want to craft better formatting and have a listicle look somehow ‘different’ from the rest of your content. Maybe you want them to have a special background color, or a different design for the header. 

    If we were doing this in the old, simple, HTML way, it would look like this:

    <dl class="listicle">
        <dt>The Title</dt>
        <dd>The Content</dd>
    </dl>

    Of course that doesn’t make anything look like a list until you put in some CSS magic:

    .listicle {
      counter-reset: listicle-counter;
    }
    
    .listicle dt:before { 
        content: counter(listicle-counter);
        counter-increment: listicle-counter;
        margin-right: 6px;
    }

    What this does is create a CSS counter and increment it every time you use <dt> creating an auto incrementing list.

    No HTML for Gutenberg

    Now, if you know the HTML, you can still do this in Gutenberg in HTML by using the HTML block.

    Example of summoning the Custom HTML block.

    But if you don’t know HTML, then this is a bloody nightmare to handle. And part of the reason why I’m very pro-Gutenberg is that it’s 2018, and expecting people to know all the myriad magical steps of HTML, in order to create a webpage, feels passé. No, it feels restrictive. We can’t expect websites to advance and become more than just pages if we don’t give them the tools.

    Complex Design

    Unlike the relatively simple javascript I used to make my Spoiler Box block, to do this requires the use of a feature called ‘Inner Blocks‘ which is effectively putting a block inside a block. 

    My original idea was to have a multiple nested block with the following:

    1. Listicle Block that calls a template of a list item
    2. ListItem block that calls a title and description
    3. ListDT block that calls the title
    4. ListDD block that allows you to add whatever more inner blocks you want

    It sounds incredibly complicated, and sadly the perfect version was so difficult I actually started with a pared down version.

    1. Listicle block that sets up a template of title and description

    The problem here is that I end up having a separate dl for each block. I can work around it with some CSS magic, but it’s not perfect. So I knuckled down and actually wrote the code that does exactly what I want. Whew.

    Some Code

    … Actually I’m going to do something I hate and that’s link to an external directory.

    I’ve build out Listicles for Gutenberg for you to peruse and fork and enjoy. Or even use if you want, becuase it does work. But it’s big. No, it’s huge for an in-post example. And to explain everything line by line wouldn’t help.

    If you go to the repository, the Gutenberg source code is located in /src/ – that’s where most (if not all) of your work will happen.

    • blocks.js – A list of all the separate JS files included
    • /block/listicle.js – The main listicle file
    • /block/listitem.js – Individual list items (this is only usable inside the Listicles block)
    • /block/listdt.js – The list title
    • /block/listdd.js – The list content (this allows you to add as many sub blocks as you can)

    And yes, not only does it auto increment as you move the slider (or bump the counter), you can flip it into reverse mode:

    The real trick of it all was the copious use of templates.

    It’s all Complicated

    Besides the fact that the blocks aren’t quite  where I want them to be, I also have to use a block builder to convert that javascript into my real files. I chose the Create Guten Block builder, instead of building out my own NPM build script.

    That too illustrates some of the concerns people have, and rightly so, about blocks being incredibly more complicated than they need to be. And yet, having gone through the exercise myself, I think that it’s perhaps not going to be that much of a drama.

    For those of use who generally just make new shortcodes, the world won’t change too much. Gutenberg supports inline shortcodes, after all, which is the majority of what people need from it. We want to be able to make our own embed blocks, which again isn’t terrible, and when someone gets to the complicated point of needing something advanced like a listicle, well eventually there will be a plugin that will handle this better than a shortcode.

    Or you could just use that HTML block.

  • More Complicated Composition

    More Complicated Composition

    Once you’ve made your basic composer.json file and you’ve got your library included, it’s time to consider a more complication situation.

    Let’s take the following example. You want to include two plugins/add-ons, a javascript library, oh and one of those plugins doesn’t use Composer. Don’t worry, we can do this.

    Organize Your Notes

    Step one is to make a list of what you need to install and where it needs to go. Because in this situation, due to the use of the javascript, we’re not going to be using that autoloader.

    1. list all your repositories 
    2. mark which ones do and don’t use Composer
    3. determine where they need to go

    For this example, we want the javascript files to go in the assets/js/ folder in our plugin, and we want the plugin/add-ons to go in plugins/

    Okay, that’s straightforward. But we need to address something.

    Avoid AutoTune

    I mentioned we’re not using the autoloader and I cited the javascript as why. That was a partial lie. You see, even though Composer makes an autoload file, and even though it is a dependancy manager, it actually tells you not to commit your dependancies to your own repository.

    The general recommendation is no. The vendor directory (or wherever your dependencies are installed) should be added to .gitignore/svn:ignore/etc.

    Official Composer Documentation

    The biggest reason why is one you absolutely will run into here, and it’s that when you add dependancies installed via git to another git repo, they end up as submodules, which are a special hell of their own. Only it’s worse. They’re not even really submodules, and you end up with empty folders.

    Seriously, I wasted two hours on this when I first ran into it.

    But that said, it’s really wise to omit your vendor folder because your plugin does not need 3 megs of files if all you want is one javascript file. Right? This means we need to add one more dependancy to Composer, and that’s Composer Copy File.

    Adding The Normal Dependancies

    Right now, our requires section looks like this:

        "require": {
            "slowprog/composer-copy-file": "^0.2.1"
        }

    So when you add in your normal dependancies, like the PHP library, you get this:

        "require": {
            "slowprog/composer-copy-file": "^0.2.1",
            "example/php-library1": "^1.3"
        }

    You’ll do this for the javascript libraries as well:

        "require": {
            "slowprog/composer-copy-file": "^0.2.1",
            "example/php-library1": "^1.3",
            "example/js-library1": "^0.2.1",
            "example/js-library2": "^2.30"
        }

    Adding the Weird Stuff

    Okay but I mentioned one of the PHP libraries I wanted to use didn’t have a composer.json file, which means I can’t include it like that. Instead, I have to add this section above the requires section, in order to create a new package to add:

        "repositories": [
            {
              "type": "package",
              "package": {
                "name": "example2/php-library",
                "version": "1.0.0",
                "source": {
                  "url": "https://github.com/example2/php-library",
                  "type": "git",
                  "reference": "master"
                }
              }
            }
        ],

    Right? That’s all kinds of weird, but basically I’m telling Composer that there’s a package called example2/php-library and it gets its data from https://github.com/example2/php-library – which means I can then add it to my requires like this:

        "require": {
            "slowprog/composer-copy-file": "^0.2.1",
            "example/php-library1": "^1.3",
            "example/js-library1": "^0.2.1",
            "example/js-library2": "^2.30",
            "example2/php-library2": "dev-master",
        }

    Copy Everything

    Once you have your files in and required, everything gets put in vendor which is great except for the part where we’re not going to use the autoloader. In fact, we’re going to add /vendor/ to our .gitignore file to make sure we keep our plugin small.

    No, we’re going to use that copy code we mentioned above like this:

        "scripts": {
            "post-install-cmd": [
                "SlowProg\\CopyFile\\ScriptHandler::copy"
            ],
            "post-update-cmd": [
                "SlowProg\\CopyFile\\ScriptHandler::copy"
            ]
        },
        "extra" : {
            "copy-file": {
                "vendor/example/php-library1/": "plugins/php-library1/",
                "vendor/example2/php-library2/": "plugins/php-library2/",
                "vendor/example/js-library1/dist/js/file.min.js":  "assets/js/file.min.js",
                "vendor/example/js-library2/dist/js/file2.min.js": "assets/js/file2.min.js"
            }
        }

    The first portion triggers a copy every time you install or update the Composer setup, and the section section (in extras) is what runs. 

    Now you include the libraries in your code like you’d downloaded and copied them, only you don’t have to worry so much about keeping them up to date. As long as you’ve got your composer versions, you’re good to go.