Creating a Link Manager with WordPress

You know I'm a huge WordPress fan and I rely on it whenever I need a CMS. At this point pretty much everything is possible, don't want to mess with PHP? Sure, use it headless with GraphQL and Next.js or the provided out of the REST API.

Today I won't be using any of the edge use cases listed above but the regular WordPress capabilities on a simply hand-crafted theme, using the Share Target API to create a native-like UX experience to share links in my Android device to social media.

If you know Pinboard, this will sound a little familiar.

What's the plan for today?

I want to create a database for all the links I share. When I want to share something the current old flow looks like this:

  1. Grab the URL of the post/site/article.
  2. Grab the title.
  3. Open Mastodon and paste both.
  4. An IFTT recipe will take my Mastodon feed and post it to my Twitter account.

Simple, sure, but we engineers like to automate all the things. That's the plan. Automate to reduce steps and increase speed.

Before this side-project I did not have a central location to store those links. Links that I usually go back to. I could use the old good browser bookmarks but that gets old fast, as soon as you reach a high number... that's unmaintainable. But even more important I need it to be searchable, you can't really search in your Twitter timeline.

Above all, I would feel very bad to spend hours curating a Twitter feed of links to suddenly get my account blocked, banned, deleted... who knows! Twitter would always have the power to mess with my content. We can't have that.

So, let's use WordPress served from my VPS to:

  • Create a Custom Post Type (to not use posts or pages, but an actual "post type link"). Yes, I know WordPress had links in the past as a Dashboard section, but those are deprecated. No longer shown by default. A new Custom Post Type is the way to go to be future proof and to make it easier to extend.
  • These items will be allowed to have Tags (taxonomy). Hashtags for the win.
  • We'll create a custom RSS Feed to customize the output, so it looks good when the links are automatically shared on social media.
  • We'll make the site a PWA to be installable on Chrome for Android and make use of the Share Target API so we can share a link from the native Android's share context menu. The PWA will show up like Signal, WhatsApp or Instagram do in the share menu.
  • We'll create a JavaScript browser bookmark to easily share any site we're browsing to the CMS.
  • Finally, we'll create an IFTT recipe that will get the site's feed and post it to Mastodon (remember I already had another recipe to repost from Mastodon to Twitter?).

Starter Theme

I always rely on Underscores to start fresh. The last update was back in September so I'd say we're pretty good. Do you know if there's another service to generate a starting theme for a given name?

Hack the Dashboard for better UI

The WordPress dashboard is good but on mobile, it takes a lot of space (personal opinion). Thankfully WordPress is so extendable that we can load a custom CSS just for the Dashboard. Specifically for our Custom Post Type. We'll hide unnecessary elements to that when I share a link on mobile I see all the fields on the viewport, without having to scroll to publish or add a hashtag.

For instance removing the (unnecessary for our use case today) media buttons on the editor:

function RemoveAddMediaButtonsForNonAdmins(){
  remove_action( 'media_buttons', 'media_buttons' );
}
add_action('admin_head', 'RemoveAddMediaButtonsForNonAdmins');Code language: JavaScript (javascript)

I've also added a custom JS file to delete more stuff upon page load, such as:

  if (_$.minorPublishing) {
      _$.minorPublishing.setAttribute('hidden', '');
    }
    if (_$.screenMetaLinks) {
      _$.screenMetaLinks.setAttribute('hidden', '');
    }
    if (_$.slugBox) {
      _$.slugBox.setAttribute('hidden', '');
    }
    if (_$.editorToolbar) {
      _$.editorToolbar.setAttribute('hidden', '');
    }
    if (_$.editorPostStatus) {
      _$.editorPostStatus.setAttribute('hidden', '');
    }Code language: JavaScript (javascript)

Full admin.js file here.

Web Share API

It's good but the documentation does not seem to match the current implementation as of today. It seems you don't get the description (???) in the query parameters.

The documentation says you can do this, but text didn't work for me.

"share_target": {
  "action": "/share-target/",
  "method": "GET",
  "params": {
    "title": "title",
    "text": "text",
    "url": "url"
  }
}Code language: JavaScript (javascript)

Also, I couldn't send a query parameter of mine from the manifest file. Why not? Not sure. Here's how I ended up setting up the share_target

{
    ....
    "start_url": "/links/",
    "scope": "/links/",
    "share_target": {
        "action": "/links/wp-admin/post-new.php?post_type=link",
        "method": "GET",
        "params": {
            "title": "title",
            "text": "url",
            "url": "url"
        }
    }
}Code language: JavaScript (javascript)

Check out the complete site.manifest

Here's how it looks on my Android device when I hit share from Firefox:

Link Manager shows like any other native Android app

Now we need to get these query params and put them into the WordPress dashboard fields (title, custom meta, description...). I've used the getLocationSearchParameters to get all the query params and then we simply add those to the form fields:

...
 _$.postTitle.value = _.queryParameters.title;
...Code language: JavaScript (javascript)

Again, I'd recomend checking the full admin.js file for all the goodies.

ServiceWorker

Chrome is smart but no so much. It needs a ServiceWorker so it can install the fake PWA. The ServiceWorker does nothing, we're just listening to the requests doing nothing with them. With this simple trick, Chrome will allow you to install the app without having to worry about actual offline support, requests caching, etc.

The ServiceWorker should be placed at the root of the site. I noticed that the manifest scope property was complaining if the ServiceWorker wasn't there.

self.addEventListener('install', (event) => {
  console.log('👷', 'install', event);
  self.skipWaiting();
});

self.addEventListener('activate', (event) => {
  console.log('👷', 'activate', event);
  return self.clients.claim();
});

self.addEventListener('fetch', (event) => {
  console.log('👷', 'fetch', event);
});Code language: PHP (php)

Custom RSS

As I mentioned I created a custom RSS with custom output, so the IFTT recipe can simply take the item content and push it to Mastodon.

Here's how to add a new ?feed=links RSS feed:

function linkmanger_add_to_custom_feed($qv)
{
  if (isset($qv["feed"]) && !isset($qv["post_type"])) {
    $qv["post_type"] = ["link"];
  }
  return $qv;
}
add_filter("request", "linkmanger_add_to_custom_feed");

function linkmanager_feed_content()
{
  require get_template_directory() . "/template-parts/rss.php";
}

function linkmanager_add_feed()
{
  add_feed("links", "linkmanager_feed_content");
}
add_action("init", "linkmanager_add_feed");

function encode_content_for_feed($content)
{
  $content = str_replace("]]>", "]]>", $content);

  return apply_filters("the_content_feed", $content, "rss2");
}Code language: PHP (php)

Now here's just a small part, so you get the idea on how I created the custom output:

...

 <?php
    $content = get_the_content();

    $description = "";
    if ($content !== "") {
      $description .= $content;
      $description .= "\n";
      $description .= "\n";
    }
    $description .= '"' . html_entity_decode(get_the_title_rss()) . '"';
    $description .= "\n";
    $description .= "\n";
    $description .= linkmanager_arrow_emoji() . " " . getLinkURL(get_the_ID());
    $description .= "\n";
    $description .= "\n";
    $description .= getLinkTagsRSS(get_the_ID(), true);
    ?>
    <description><![CDATA[<?php echo $content; ?>]]></description>
    <content:encoded><![CDATA[<?php echo $description; ?>]]></content:encoded>

....Code language: HTML, XML (xml)

Check the full RSS template.

Magic bookmark

With this JavaScript browser bookmark it will take you to the WordPress Dashboard passing the URL, meta title, and meta description to the creation screen.

With this it only takes 2 clicks to share a link:

  • 1 on the bookmark.
  • 1 on the publish button.
javascript:window.location=`YOUR_SITE/wp-admin/post-new.php?post_type=link&title=${encodeURIComponent(document.title)}&url=${encodeURIComponent(window.location.href)}&description=${document.querySelector('meta[name="description"]')?.content ? document.querySelector('meta[name="description"]')?.content : ''}`Code language: JavaScript (javascript)

Fork me

Of course, all the code is open source and for you to grab, fork, and tweak to fit your needs.

https://github.com/quicoto/link-manager

Closing words

A weekend project I'm really happy about. It is now for me super convenient to share links. No manual copy and pasting thanks to the bookmark and the Android share integration. Plus the links are stored on my server, as long as I want. I can search and tag them for easier access later on.

Finally, here, check out all my shared links!

PD: Own your content.

Leave a Reply

Your email address will not be published. Required fields are marked *