Duplicate Featured Image Remover

Origin Story

In a number of scenarios we’ve used plugins to use the first image in the post as the featured image. That’s nice in that if people forget or are unaware of the featured image it’ll happen automatically and that fits in nicely with how various themes use featured images in various layouts. Where it doesn’t work as nicely is when the featured image is shown in a way that feels duplicative in the single post view. Something like below. I was reminded of one way to solve this after reading Alan’s post yesterday. I’m also trying to do a better job of blogging more1, commenting more, and linking back to posts.2 Who knows maybe people will start believing in RSS readers again?

A screenshot from codepen showing one example of how featured image duplication can look awkward.

Function

For this example I just grabbed the HTML from the base Understrap template and set a featured image and put it in the post body as well. The exact nature of the javascript will change depending on how the theme is setup but this is fairly generic stuff and would likely work on a variety of themes. I commented up the few lines involved so you can see what each one does.

let featured = document.querySelectorAll('img')[0];//find all the images and then select the first image (you'd change this on a theme that has a detectable header image to 1 or whatever
let content = document.getElementsByClassName('entry-content')[0];//find the div with the class entry-content (you'd change this to the class or id of your content div if your theme is different)

let firstImg = content.querySelectorAll('img')[0]; //find the first image in the div with the class entry-content (you'd change this to the class or id of the content if your theme is different)

if (featured.src === firstImg.src){ //this says if the img src of the featured image and the first content image match do the following
  //firstImg.classList.add('hidden') //option for hiding it rather than removing it assuming you have a class hidden with display:none or something similar 
  firstImg.remove(); //remove the first image 
}

Here’s the codepen with the whole thing. I’m repeating my pattern of just copying code there to make life easier for sharing examples like this.

See the Pen
wordpress featured image duplicate remover
by Tom (@twwoodward)
on CodePen.

Getting Javascript in Your Site

You have a few choices when it comes to adding javascript like this into your theme.

The easiest option is to add a plugin that lets you add javascript. Something like Header/Footer Scripts. Just make sure you save anything complex somewhere you can find it in case this gets deleted or whatever. Trust me on this one.

If you can’t or don’t want to use a plugin like that you still have some options. While you could append your code to your theme’s existing javascript, you’ll lose it on any upgrades and have to re-write it. That’s a pain. I’d suggest writing a tiny plugin. I promise it’s fairly pleasant for something like this and doing it will make you feel powerful and full of code-y goodness.

To make this work you need two things. The first is the PHP file that that says “Hi, I’m a plugin. Let me tell WP what to do.” and then loads your javascript. Secondly, you need your javascript. Here’s a link to super minimal template for a WordPress plugin that loads one javascript file.

The comments below tell you what the pieces do. The javscript will be whatever you put in the file but by default it will say “hello” in the console. I do that to make myself feel better that all is working as planned. Install it, activate it, and you’re 90% of the way there.

The PHP looks like this.

<?php 
/*
Plugin Name: SUPER UNIQUE JS PLUGIN
Plugin URI:  https://github.com/
Description: For stuff that's magical
Version:     1.0
Author:      YOU!!
Author URI:  http://YOURSITE.COM
License:     GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Domain Path: /languages
Text Domain: my-toolset

*/
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );//helps keep out bad people


add_action('wp_enqueue_scripts', 'super_unique_load_scripts');//

function super_unique_load_scripts() {                           
    $deps = array('jquery');// you can use jquery with this just remember to use jQuery instead of $ (or alias it)
    $version= '1.0'; 
    $in_footer = true;//loads in footer    
    wp_enqueue_script('super-unique-main-js', plugin_dir_url( __FILE__) . 'js/super-unique-title-main.js', $deps, $version, $in_footer); 
}

1 Even just random little things like this.

2 Trackbacks can be more than just spam.

Gravity Forms Notification to Google Spreadsheet

The idea that data can flow to different places for different purposes is one of the key concepts I want people to believe in. Different technologies and different interfaces have different affordances depending on what you’re trying to do.

In this case, we’ve built some online training for students. As part of that training they need to sign off indicating they read various rules and safety advice. We’re using Gravity Forms to collect that information. We’re going to set a special notification email that’s easier to parse in addition to the regular email that gets sent out (that one is oriented towards student confirmation and alerting the individual faculty).

Gravity Forms Notification

We’re just going to put the student email and faculty email in the subject line with a space between them. I did some fancier stuff early but went back to this when I realized what we were doing just wasn’t complex enough to justify extra drama. I set the from name to Health Hub Logger so it’d be easier to write the filter in GMail.

Notifications in Gravity Forms are pretty straight forward but you can find out more on their site.

Screenshot of the Gravity Forms notification interface indicating that the students and faculty email are in the subject line of the email via variables.

GMail Filter

Screenshot of gmail filter interface showing that I want these emails marked as read and labeled as "HealthHub".
I then setup a filter in GMail so that I could be confident that the Google Script could find these emails and that I would not really see them. I search for anything from the Health Hub Logger name and make it as read and label it with the “HealthHub” tag.

Google Script

Now comes the Google Script. I open a spreadsheet. Go to Tools>Script Editor and put in the following. After that, I set the trigger in the Google Script editor interface to run every 15 minutes.

function healthHubLogger() {
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheets()[0];
  
 var today = new Date();
 var dd = today.getDate()-1;
 var mm = today.getMonth()+1; //January is 0 DO NOT FORGET THIS
 var yyyy = today.getFullYear();
 var yesterday = yyyy + '/' + mm + '/' + dd;
  
  var query = "after:" + yesterday  + " label:HealthHub";// not necessary to restrict date really but I figure it's faster -- note the HealthHub label
  
  var threads = GmailApp.search(query);
  
  var allSubjects = sheet.getRange("C:C" + sheet.getLastRow()).getValues();
  var flatSubjects = allSubjects.map(function(row) {return row[0]});
  
  for (var i = 0; i < threads.length; i++) {
    var messages = threads[i].getMessages();
    //Logger.log(messages);    
    for (var m = 0; m < messages.length; m++) {
       var healthLog = [];
     
      var from = messages[m].getFrom();
      var to = messages[m].getTo();
      var time = messages[m].getDate();
      var subject = messages[m].getSubject();
      var student = subjectSplitter(subject,0);
      var faculty = subjectSplitter(subject,1);
      var mId = messages[m].getId();
      
      var mYear = time.getFullYear();
      var mMonth = time.getMonth()+1;
      var mDay = time.getDate();
      var messageDate = mYear + '/' + mMonth + '/' + mDay;
      if(flatSubjects.indexOf(subject) < 0 ) { 
        healthLog.push(from);
        healthLog.push(time);
        healthLog.push(subject);
        healthLog.push(student);
        healthLog.push(faculty);
        healthLog.push('https://mail.google.com/mail/u/0/#inbox/'+mId);
        sheet.appendRow(healthLog);      
     }
    }

  }          

}


//split subject line
function subjectSplitter(subject,num){
  var emails = subject.split(" ");
  return emails[num];
}

Now all that I need to do is share the spreadsheet with the program administrators and they have an easy way to see what’s what without having to go into WordPress or get any additional accounts.

H4 Widget Headers – Incorrect Nesting Fix

Nothing fancy here but I’m documenting it for the benefit of beginners or people who don’t do this at all.

It seems most of the widgets in WordPress automatically give you h4 headers when you add them to the sidebar. That seems to go against the idea of nested and orderly headers that WCAG accessibility wants. Granted, I’m not an expert and reading their documentation seems more painful than it should be. I do know that the SiteImprove checker flags it on the site I’m working on so I turned to javascript for a fix.

What I need to do is take the HTML below and remove the H4 element and replace it with a div. I add a class so it’s easier for me to repeat the style that the H4 element had.

<div id="tag_cloud-2" class="widget widget_tag_cloud">
    <h4 class="widgettitle">Tags</h4>
    <div class="tagcloud">
<!--bunch of stuff removed for simplicity's sake-->
    </div>

As is typical for me, I did it on one element first and then I move to see if I can make a more generalizable function.

if (document.getElementById("tag_cloud-2")) {
   let tags = document.getElementById("tag_cloud-2");//gets the existing widget chunk
   let div = document.createElement("div");//makes the new div
   div.innerHTML = "Tags";//sets the inner HTML of the div to tags
   div.classList.add("widgettitle");//adds the class
   tags.replaceChild(div, tags.querySelector('h4'));//gets the first h4 element of the widget div and replaces it with the div we just made
 }

Now I need to repeat this for the other 3 or 4 widgets. I could just copy it over and replace it but I’m really only dealing with two variables so making a decent function makes sense. I need to select the specific ID of the widget and I need specific replacement text. That leads to a function like this.

function replaceWidgetTitles(id,text){
  if (document.getElementById(id)) {
    let widget = document.getElementById(id);
    let newTitle = document.createElement("div");
    newTitle.innerHTML = text;
    newTitle.classList.add("widgettitle");
    widget.replaceChild(newTitle, widget.querySelector('h4'));
  }
}


replaceWidgetTitles('tag_cloud-2','Tags')
replaceWidgetTitles('recent-posts-2','Recent Posts')
replaceWidgetTitles('wpp-2','Popular Posts')
replaceWidgetTitles('archives_calendar-2','Archives')

See the Pen
replace tags h4 with div
by Tom (@twwoodward)
on CodePen.

Accessibility Bandage

We’re trying to solve some accessibility issues in sites that already exist. It’s not much fun but some of the solutions might be useful to others. The goal is also to do it pretty fast as many of these sites aren’t really our responsibility but we’re trying to help people out who are on WordPress.

Same Name, Different URL

In this case we had an issue where some tags for the posts were the same name as some other links on the page. They didn’t go to the same place. That’s a bad thing for accessibility. Handily, our post tags were wrapped in a div with the class tags (.tags).

The following bit of js, selects all the a elements within the element with tags class (.tags) and adds a the # character to the tag title. Simple and easy compared to writing a custom tags function in php. I also like the ability to use querySelectorAll in combination with forEach.

let tags = document.querySelectorAll('.tags a')
tags.forEach(function(tag){
  tag.innerHTML = '#' + tag.innerHTML; //get original name and just add a #
})

See the Pen
cobe tags accessibility fix
by Tom (@twwoodward)
on CodePen.

Pattern

What you can see too in the codepen above is that I’ll often copy/paste the HTML from the WordPress site and experiment with it in codepen. It often simplifies things while providing a quick working environment.

Making an Index Using Javascript

Working with a faculty member we had a rather long page that was originally written in Google Docs. It had many sections that were (mostly) designated by H tags of various denominations. The goal was to and put it on a website quickly build an index of anchor links. I did not wish to do the index portion by hand.

With javascript things like this are relatively pleasant. You can see the whole thing in this codepen but I’ll break it down a bit below.

First we can get all the H tags with querySelectorAll.

let headers = document.querySelectorAll("h2, h3, h4, h5, h6")

I can console.log(headers) and I’ll see a NodeList of all the headers it found. I tend to work console.log all my variables as I go just to make sure it’s really happening the way I think it is.

My next move is to add an id to each of these headers so that we can navigate to them via anchor links. with this forEach loop each header will get an id of header-whatever number we’re on in the loop.

let i = 1;
headers.forEach(function(header) {
  header.id = "header-" + i;
  ++i;
});

Now that I have headers that I can link to as anchor links, I need to build the index and put it somewhere.

In this case it was easy for me to add a div to the source manually so I did. That will be where my index ends up.

    I’ll get that div as a destination.

    let indexHolder = document.getElementById('index');
    

    I’ll assign a variable (indexHtml) to hold a string of HTML which will be all the li tags with the links and titles.

    We’ll go back to our forEach (cleaned of other stuff for clarity) loop and use it to build out that HTML.

    let i = 1;
    let indexHtml = '';
    headers.forEach(function(header) {
      indexHtml = indexHtml + '<li><a href="#header-' + i + '">'+header.innerHTML+'</a></li>' ;
      ++i;
    });//
    indexHolder.innerHTML = indexHtml;
    

    Now that we have the string, we can assign it as the innerHTML of the index div we created earlier.

    indexHolder.innerHTML = indexHtml;
    

    The whole thing looks like this.

    let headers = document.querySelectorAll("h2, h3, h4, h5, h6")
    let i = 1;
    let indexHtml = '';
    let indexHolder = document.getElementById('index');
    headers.forEach(function(header) {
      header.id = "header-" + i;
      indexHtml = indexHtml + '<li><a href="#header-' + i + '">'+header.innerHTML+'</a></li>' ;
      ++i;
    });//
    indexHolder.innerHTML = indexHtml;