Working through displaying Webmentions

Now that this site supports Webmentions, I’ve been having some fun digging into how I’d like them to be presented.

The theme I’m using is very bare-bones. I created it using Underscores a couple years ago when I decided I had lost touch with the code I was using and for some reason wanted to get back in touch? I don’t know, but I like it.

I created a plugin specifically for my adjustments to the IndieWeb, Webmention, and Semantic Linkbacks plugins. There are a couple scripts and styles I decided I didn’t need as well as a custom comment walker I decided to remove.

At the same time, I created another plugin specifically for adjustments to WordPress. I removed all default emoji handling to see if things would display better in feed readers and adjusted some of the markup output by default—sorry, Windows Live Writer.

Back to comments.

Before I started, the theme’s comment template output everything stored in the comments table as if it was a comment. This makes sense because WordPress doesn’t have a complete idea of “custom comment types”, so it treats them all the same.

The first thing I did was break the query for all comments into comment types so that I could display them in separate sections. WordPress has a function that should do this, separate_comments(), but it is hard coded to support only comment, trackback, pingback, and pings. I have my eye on proposing a filter there, but I’m guessing there are wider concerns with comment types that need to be addressed at the same time.

I’ll revisit that code in the near future. I was just winging it today and never actually went back to review some of those decisions. 🙂

I’m starting with a section for “Likes”, a section for “Mentions”, and a section for “Replies”. Once I get more familiar with other actions and how I see them being used, I’ll likely mix things up a little more.

For Likes and Mentions, I decided to keep things simple and display the avatar, name, action, and date. This displays a bit more than a facepile, but less than a full comment.

It’s been interesting seeing what data actually comes along with a Like or a Mention—sometimes it’s the full post—and it’d be fun to figure out the right way to display that in the future. I could see manually editing some mentions and displaying them differently so that the context is displayed. I envision something like the first 100 characters before and after the actual mention itself.

I removed support for comment navigation, as it’s something I don’t want to deal with right now and I have the option to paginate comments turned off in WordPress.

I was low-key annoyed that wp_list_comments() only supported a style of ol or li, but then went further to the comment walker and saw div was also a supported option. I created a new trac ticket for that and tagged it as a good first bug for anyone who may want to contribute.

My next annoyance is that comment author names are wrapped with <b> and so here I am deciding that of course I should add a custom walker. Exciting!

Of course now I’ve screwed this all up somehow and the reply links aren’t working.

Ahhh, I see! I tried to shrink the amount of markup used around each comment, but the default reply script used in WordPress expects a bit more structure. Added that back for now.

I’ve temporarily adjusted the “Reply” text below each comment to “Reply to {comment author name}”. I think I’d like to adjust this even more so that on nested comments, it includes everyone’s name. Something like “Reply to Jeremy Felt, Alice, and Bob” on a thread in which all have participated would be cool. Part of me wants to shorten that to first names only, though that starts to make assumptions.

One quirk now that I have this all in place is that I’ve replied to “mentions” in the past, which are now displayed in their own area of the page. These replies are now displayed outside of that context. I’m guessing this won’t be an issue in the future, though if I ever do want to reply to a mention, it’d be cool if there was a way to convert the mention into a reply for purposes of display. I’ll think some more on that.

Almost there. Time for another bad decision.

It looks like some multi-paragraph Webmentions are coming in or being stored without paragraph tags. Rather than dive into really testing that, I took the lazy way out and replaced single line breaks with double line breaks so that wpautop() can add paragraphs as intended. This seems to have worked, but I’m sure I’ll run into an edge case at some point.

Okay! Theme deployed. Deployments setup for the new plugins. All is right.

Version 0.0.2 of a new vibe for Webmentions is live on the site. 🕺🏻

Variable SSL certificate directives in Nginx

Update, April 28, 2019: Warning: I don’t think that this works. A few days after getting this setup, HTTPS broke on a few of my sites and I had to undo this config. I haven’t had enough time to poke at things, but JJJ pinged me yesterday and said he was having issues to. I hope to get back to this soon, but for now—beware! 🙂

When I left WSU, we had hundreds of server blocks defined for hundreds of domains that pointed to a single WordPress multisite installation. I wrote this up previously, but each of these blocks was effectively the same:

server {
    include common-listen.conf;

    root /var/www/wordpress;

    ssl_certificate /etc/nginx/ssl/;
    ssl_certificate_key /etc/nginx/ssl/;

    include common-ssl-config.conf;
    include common-location-block.conf;

The only reason different server blocks were required was the directives for ssl_certificate and ssl_certificate_key. As we were getting prepped to launch our first sites, I wrote about my disappointment that variables were not supported.

Fast-forward to Tuesday. I no longer support hundreds of unique domains, but I still think it’s cool to see that Nginx 1.15.9 added support for variables to the to the ssl_certificate and ssl_certificate_key directives.

I upgraded this evening on my personal server and then did a brief test with a multi-tenant WordPress configuration that I manage and everything worked exactly as expected.

This is the server block that I used: (It now works with 4 domains)

server {
    listen 443 ssl http2;
    server_name _;
    root         /var/www/wordpress;

    ssl_certificate.    /etc/letsencrypt/live/$server_name/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$server_name/privkey.pem;

    include /etc/nginx/ssl-common.conf;
    include /etc/nginx/wp-shared-location-common.conf;
    include /etc/nginx/php-location-common.conf;

I haven’t really explored the consequences of using _ as the server name, but with this configuration—just one server block—I can support as many different domains as I want as long as they have matching Let’s Encrypt generated certs.

Pretty cool!

Things I’m learning while playing with Gutenberg (part I)

I’ve been having fun working heavily with Gutenberg over the last several weeks. As I ran into a couple things, I started tracking them in a draft post so I could work through and publish them later.

Here’s the first set. 🙂

How to access className from a block’s save() function

The classname attribute is available to a block’s edit() function by default, but it is not available to the block’s save() function. I learned from this GitHub issue that registering className as one of the block’s attributes will provide it to the save() function.

registerBlockType( 'my/block', {

	attributes: {
		className: {
			type: 'string',
			default: '',

	edit( props ) {
		// className via props
		const { attributes, className } = props;
		const { customData } = attributes;

		return ( // ... );

	save( { attributes } ) {
		// className via attributes 
		const { className, customData } = attributes;

		return ( // ... );
} );

Be careful importing Lodash modules

If you want to use something from Lodash, like find():

// Import the module like this
import find from 'lodash/find';

// Not like this...
import { find } from 'lodash';

The latter brings in the global _ from Lodash, which overrides the global _ provided by Underscore and causes trouble.

A this.activateMode is not a function error that prevented media blocks from being used led me to this issue, which helped me.

Beware of non-changing arrays

I ran into this a couple different ways recently and I’m still working on understanding it completely, but things like Array().push and Array().pop modify the reference value of an existing array and may not trigger the state change you’re expecting.

let apples = [];
let oranges = apples;

oranges; // []

apples.push( 1 );
oranges; // [ 1 ]

oranges === apples; // true
oranges === apples.concat( 1 ); //false

I picked up on better approaches for this via this issue when a block’s attributes weren’t saving as expected.

Remove an administrator from 58 networks at once with WP-CLI

Ok, so this is super specific and probably won’t come in handy for you. But it’s another example of how quickly you can perform a task using WP-CLI.

At WSU, we currently have 58 networks configured in WordPress multisite.

  • Global administrators are users that have been set under the site_admins option on network 1.
  • Network administrators are users that have been set under the site_admins option on any other network.

Depending on the page view, these are used to make up what WordPress validates as is_super_admin(). See WordPress core ticket #37616 for an eye into how this will get a lot better soon.

One of our excellent interns has graduated and we needed to remove him from ~25 different networks. Because I rushed the original UI on adding network administrators, there’s no great way to remove them.

Enter WP-CLI:

wp site option list --field=site_id --search=site_admins | xargs -n1 -I % wp eval 'if ( $sa = get_network_option( %, "site_admins" ) ) { if ( $tk = array_search( "old.username", $sa ) ) { unset( $sa[ $tk ] ); update_network_option( %, "site_admins", $sa ); } }'

WP-CLI isn’t really multi-network aware out of the box, so the wp site option list command will output all options for all networks. We can use this to our advantage by having only site_id output for only the site_admins option on each network.

We then pipe this to xargs and use wp eval to run arbitrary PHP code. This code retrieves the current site_admins data for the given network, removes the old user from the array, and then updates the network option with the new value.

This is a prime candidate for packing into some code and turning it into an actual command—wp network admin remove or something—but it does the trick for now!

And if we haven’t written a better UI or WP-CLI command by the time somebody else needs to be removed, then referring to this post and using the command will take 10 seconds instead of the 5 minutes it  took to make it in the first place. 🙂

Update: Daniel pointed out wp super-admin, which I hadn’t noticed before. This will take a --url argument, so if you know a URL on the network, then you can pass that and it will work. There’s no great way (that I know of yet) to generate list of URLs that covers all networks, but I started an issue on the WP-CLI repo to start figuring that out.

Add users from one site to another on multisite by role with WP-CLI

Today I wanted to make sure a bunch of editors from one site existed as editors of a new staging site that we’re building out. Both sites exist as part of the same multisite network.

Thanks to WP-CLI and xargs, this is pretty straight forward:

wp user list --role=editor --field=user_login | xargs -n1 -I % wp user set-role % editor

This tells WP-CLI to list only the user_login field for all of the editors on It then passes this list via pipe to xargs, which runs another wp command that tells WP-CLI to set the role of each user as editor on

Because users are already “created” at the global level in multisite, they are added to other sites by setting their role with wp user set-role.

I’d estimate that with a list of 15 users, this probably saved closed to 15 minutes and didn’t require a whole bunch of clicking and typing with two browser windows open side by side.

Props to Daniel’s runcommand post for providing an easy framework.