WPlus WordPress Theme

Origin Story

There once was a group who really liked Google Plus. With the demise of the service, they were unhappy. With this request, I wondered if we might just build a very similar experience in WordPress. I think I ended up getting pretty close.

The Look

The theme is built using our normal pattern of bending Understrap so it uses Bootstrap at its core. The theme is here.


I don’t know if this works decently or not (vertical slider gets kind of lost) but I took a shot at making the WordPress theme comparable to the Google Plus layout using juxtapose. If you’re anything like me, you have no real memory of what Google Plus looked like and I want you to appreciate this. Luckily I still have access to G+ through my VCU account for at least a while longer.

The Masonry Grid

I didn’t think too hard when I opted to make the initial masonry layout.1 I used what seemed like a really handy path. What I didn’t pay attention to was that this particular masonry-sort goes top to bottom and then left to right. Since that was brought to my attention, I’ve gone back to the G+ layout and tried to see more logic behind how they did it and what the sort order is but I end up confused. I’m leaving it as it is for now but I might go back and re-code it in the future.2

The Feel

The front-end publishing was one major element that required a good deal of work. I hadn’t done much with tinymce and it resulted in a good bit of wandering and general irritation although I now know a good bit that will likely be handy down the road. For instance, I wasn’t paying attention and kept trying to use recent plugins without noting that WP uses the old version of tinymce which has a different structure for plugins. I also found the paste cleansing options that I didn’t know existed.

TinyMCE & the Bootstrap Modal

This series of pieces gives me the WP Editor on the front end.

//make tinymce settings
function plus_post(){
	$content = '';
    $settings =   array(
            'wpautop' => true,//double line breaks become paragraphs
            'editor_height' => '400',
            'media_buttons' => true,
            'selector' => 'textarea',
            'tabindex' => '5',
            'editor_css' => '', 
            'editor_class' => '',
            'textarea_name' => 'pluscontent',//id of element where you want the editor
            'paste_remove_spans' => true,//hey look remove spans!
            'teeny' => false,
            'dfw' => true,
            'tinymce' => true,
            'quicktags' => false,
            //'plugins' => 'tinymceEmoji',//while I removed the plugin this where you can add them just make sure they're for the old version
            'tinymce' => array(
		         'toolbar1'=> 'bold,italic,underline,link',//loads the various buttons we want
		         'toolbar2' => '',
		         'toolbar3' => '',
		         'content_css' => get_stylesheet_directory_uri() . '/css/front-editor-styles.css',
		    ),
            'drag_drop_upload' => true, 
            );
    return wp_editor( $content, 'mypluspost', $settings); 
}


add_action( 'pre-html-upload-ui', '_force_html_uploader' );

function _force_html_uploader( $flash ) {
    remove_action('post-html-upload-ui', 'media_upload_html_bypass' );
    return false;
}

add_action('media_upload_tabs', '_media_upload_auto_insert_js');

function _media_upload_auto_insert_js(){
    ?><script src="<?php bloginfo('stylesheet_directory'); ?>/js/upload.js"></script><?php
}

Now what isn’t immediately obvious is that we’re using this in a Bootstrap modal and the plugin itself uses some elements that are like modals (the link adder for instance) and that leads to stuff getting messed up. Luckily we can fix it with a bit of javascript.

//this deals with the modal tinymce issues
jQuery(document).on('focusin', function(e) {
    if (jQuery(e.target).closest(".wp-link-input").length || jQuery(e.target).closest("#link-selector").length) {
        e.stopImmediatePropagation();
    }
});

A modal popup that shows someone pasting a URL into the modal editor. On return, the image, title, and description expand automatically.

Open Graph Unfurling

WordPress does the oembed expansion well. What G+ did in addition to this was grab the Open Graph data and unfurl3 those links.

To do this, we have to do a couple of things. The first is to watch the tinymce editor for URLs on a line by themselves.

jQuery(document).ready(function($) {

    tinymce.PluginManager.add('keyup_event', function(editor, url) {

        editor.on('keydown', function(e) {
			if(e.keyCode == 13){ //on return we check
	            get_ed_content = tinymce.activeEditor.getContent();
	            openGraphMatch(get_ed_content);
	        }
        });
    });


    function openGraphMatch(content) {        
        processOgText(content);
    }
});

That code watches for the return key and looks for lone URLs.

The code below goes to a site, gets the returned data and builds a little chunk of HTML to make the returned data look pretty.

function processOgText(content){
	let getResults = getUrls(content)

	function getUrls(value) {
	  let regEx = /(?:(?:https?|ftp|file):\/\/|www\.|ftp\.)(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#\/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|[A-Z0-9+&@#\/%=~_|$])/igm;
	   let urls = value.match(regEx)
	  return urls;
	}


	getResults.forEach(function(link) {
	  let theLink = link;
	  //maybe do this in a smarter way - excluding oembed supported services
	  if (theLink.includes("youtube.com") || theLink.includes("twitter.com") || theLink.includes("vimeo.com") ){
	  	return;
	  }
	  let url = 'https://bionicteaching.com/tools/open-graph-api/?url=' + link

	  fetch(url)
	    .then(function(response) {
	      return response.json();
	    })
	    .then(function(myJson) {
	      var data = JSON.stringify(myJson);
	      console.log(data)
	      makePreviews(content,JSON.parse(data),theLink);
	    });


	  function makePreviews(destination, data, theLink){
	    //let destination = document.getElementById('furl-list');
	
	    let theName = '';
	    if (data.hasOwnProperty('title') && data.title != null){
	       theName = data.title;
	    }
	    
	    let description = ''
	    if (data.hasOwnProperty('description') && data.description != null){
	        description = data.description;
	        }
	    
	    let img = 'https://via.placeholder.com/150';
	    if (data.hasOwnProperty('images') && data['images'].length != 0 && data['images'] != null){
	      img = data.images[0].url;
	    } 
	    if (data.siteName === null && data.title === null && data.description === null){
	    	return;
	    }
	    let text = '<div class="furl-content"><img class="furl-img" src="'+img+'"><h2 class="furl-name"><a href="'+theLink+'">' + theName + '</a></h2>';
	    //text += '<div class="furl-title"><p><a href="'+theLink+'">' + title + '</a></p></div>';
	    text += '<p class="furl-description"><a href="'+theLink+'">'+description+'</a></p></div>';
	    let currentText = tinymce.activeEditor.getContent();
	    let urlRemoved = currentText.replace(theLink,'')
		tinymce.activeEditor.setContent(urlRemoved + text, {format: 'raw'})
	  }
	});
}

I’m still messing with the API point that returns the Open Graph data. I saw Open Graph on GitHub and modified it a bit in case the page didn’t support Open Graph.


//from https://github.com/fusonic/opengraph
require "vendor/autoload.php";
use Fusonic\OpenGraph\Consumer;

header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");


$site = $_GET['url'];
$exists = site_exists($site);

if ($exists != false){
	$consumer = new Consumer();
	if($consumer->loadUrl($site)){
		$object = $consumer->loadUrl($site);
	}
}

if($object){
	if (!$object->title){
		$object->title = page_title($site);
	}
	if (!$object->url){
		$object->url = $site;
	}
	$json = json_encode($object);
	echo json_encode($object)."\n";
}


//consider replacing w https://github.com/mpyw/opengraph to deal with php versions

function site_exists ($site){
	$file_headers = @get_headers($site);
	if(!$file_headers || $file_headers[0] == 'HTTP/1.1 404 Not Found') {
	    return false;
	} else {
		return true;
	}
}


//from https://stackoverflow.com/a/54595358/3390935
function page_title($url) {
  $title = false;
  if ($handle = fopen($url, "r"))  {
    $string = stream_get_line($handle, 0, "</title>");
    fclose($handle);
    $string = (explode("<title", $string))[1];
    if (!empty($string)) {
      $title = trim((explode(">", $string))[1]);
    } else {
    	return $url;
    }
  }
  return $title;
}

That will spit you back a chunk of JSON like this.

{
"audios": [],
"description": "Barry made landfall as a Category 1 hurricane early Saturday afternoon along Louisiana's central coast, then immediately weakened back into a tropical storm, according to the National Hurricane Center.",
"determiner": null,
"images": [
{
"url": "https://cdn.cnn.com/cnnnext/dam/assets/190713131155-04-barry-weather-0713-mandeville-super-tease.jpg",
"secureUrl": null,
"type": null,
"width": 1100,
"height": 619,
"userGenerated": null
}
],
"locale": null,
"localeAlternate": [],
"richAttachment": null,
"seeAlso": [],
"siteName": "CNN",
"title": "Barry moves deeper into Louisiana with more rain on the way",
"type": "website",
"updatedTime": null,
"url": "https://www.cnn.com/2019/07/13/us/barry-louisiana-saturday-wxc/index.html",
"videos": []
}

Drag/Drop Media Upload

An image is dragged into the editor and it uploads and embeds without any clicking.
This gets a little closer to what I want. You can drag a file into the editor view (tiny mce settings ‘drag_drop_upload’ => true) and it’ll upload and auto-insert. I’d like to figure a way to remove the media library view but haven’t done that yet. The js below fakes the insert button.

//auto insert image on drag upload https://wordpress.stackexchange.com/questions/167143/insert-image-automatically-when-upload-finishes-wordpress-media-uploader
//to do - try to deal w making media library view not happen
jQuery( document ).ready(function() {
    typeof wp.Uploader !== 'undefined' && wp.Uploader.queue.on( 'reset', function () {
        // From the primary toolbar (".media-toolbar-primary")
        // get the insert button view (".media-button-insert")
        // and execute its click (as specified in its options).
        //document.getElementById('__wp-uploader-id-2').style.display = 'none';
        wp.media.frame.toolbar.get('primary').get('insert').options.click();
    } );
});

The Fabric of Our Lives

What this kicked off for us was a larger idea around creating WordPress versions of things, all kinds of things. So expect a bunch of tools that live in WordPress where you can control your own data. They won’t be exact matches for these external tools but they’ll be interesting and useful ways to move your tools to places you control.


1 Masonry (like bricks) is a fancy way of saying it all fits together even if the pieces are of different heights. This has been a hassle in HTML-land but has gotten easier. Masonry will help you find other solutions.

2 This often means never but my intentions are good.

3 I like the word because it reminds me of flags.

Comments on this post

  1. aarondavis1 said on August 13, 2019 at 7:06 am

    Tom, this theme looks fantastic. Will need to spin up a site and have a look. What I like is possibility to embed media that may not have worked so well in Google+. I wonder if it would offer a social media platform in a similar fashion to P2.

    Also on: Read Write Collect

    • Tom Woodward said on August 13, 2019 at 1:28 pm

      Thanks Aaron.

      It’s been a learning experience but mostly a fun one and it has room to grow. I see some potential to use it with multisite to create communities as individual sites with particular topics as a focus.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Trackbacks and Pingbacks on this post

No trackbacks.

TrackBack URL