<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SitePoint</title>
    <description>The latest articles on DEV Community by SitePoint (@sitepoint).</description>
    <link>https://dev.to/sitepoint</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2394%2Fb9758860-c2b8-4653-8e11-e1e52af86ce0.png</url>
      <title>DEV Community: SitePoint</title>
      <link>https://dev.to/sitepoint</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sitepoint"/>
    <language>en</language>
    <item>
      <title>Why We Moved a 20-Year-Old Site to Gatsby</title>
      <dc:creator>Stuart Mitchell</dc:creator>
      <pubDate>Mon, 13 Jul 2020 06:11:45 +0000</pubDate>
      <link>https://dev.to/sitepoint/why-we-moved-a-20-year-old-site-to-gatsby-57ig</link>
      <guid>https://dev.to/sitepoint/why-we-moved-a-20-year-old-site-to-gatsby-57ig</guid>
      <description>&lt;p&gt;We knew we had a problem.&lt;/p&gt;

&lt;p&gt;In 2019, SitePoint was getting Lighthouse Speed scores under 10 on mobile, and between 20 and 30 on desktop.&lt;/p&gt;

&lt;p&gt;Our efforts to control UX bloat were failing in the wake of a publishing business environment that sprang new leaks just as we’d finished temporarily plugging the last one. Our reliance on advertising, controlled by external parties, was a major obstacle to improved site performance. Our traffic growth had turned into decline.&lt;/p&gt;

&lt;p&gt;On a site that provided people with a place to come and learn to code with best practices, this was not a good look. And it wasn't a site we could feel proud of, either.&lt;/p&gt;

&lt;p&gt;To make matters worse, operational bottlenecks had arisen that made adaptation a tricky logistical business. Our team was struggling to make changes to the site: having focused on our Premium experience for several years, we were down to one developer with WordPress and PHP experience. To test out code changes, the team would have to wait in a queue to access our staging server.&lt;/p&gt;

&lt;p&gt;It wasn’t energizing work for anyone, and it certainly wasn't efficient.&lt;/p&gt;

&lt;p&gt;It was time to make some changes, and we set out to look for a solution. After a lot of research, we decided that &lt;a href="https://www.gatsbyjs.com/"&gt;Gatsby&lt;/a&gt; would be a great fit for our team. It would play to our talent strengths, help us solve all of the issues we had identified, and allow us to keep using WordPress for the backend so the editorial process wouldn’t need to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Moved to Gatsby
&lt;/h2&gt;

&lt;p&gt;Early in the research process, Gatsby started to look like a serious frontrunner. SitePoint isn't a small site, so we knew that the tech we chose had to be able to handle some pretty intense demands. Gatsby checked all of our boxes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We could code everything in &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;, a tech that every member of the front-end team knows and uses daily.&lt;/li&gt;
&lt;li&gt;Gatsby is super fast at its core — performance was at the heart of this project, and we could start from a good footing.&lt;/li&gt;
&lt;li&gt;The entire site is rendered as static, which would be great for SEO.&lt;/li&gt;
&lt;li&gt;We could build it as a new project, which meant no worrying about the existing codebase, which brought a huge amount of legacy code with it.&lt;/li&gt;
&lt;li&gt;We could use Gatsby Cloud, allowing the team to get feedback on the build at any time just by pushing the branch to GitHub.&lt;/li&gt;
&lt;li&gt;DDoS attacks on WordPress wouldn’t cause us issues, as the front-end is completely stand-alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  More Maintainable CSS with &lt;code&gt;styled-components&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Since we were going to rebuild the site from scratch, we planned to make some design changes at the same time. To help with this work we decided to use &lt;a href="https://styled-components.com/"&gt;styled-components&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;styled-components keeps the site's styling easy to maintain, and we know where to look when we want to change the style of something — the style is always with the component.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Made the Build Happen
&lt;/h2&gt;

&lt;p&gt;We started by following Gatsby's basic docs and pulling in our posts with the &lt;code&gt;gatsby-source-wordpress&lt;/code&gt; plugin.&lt;/p&gt;

&lt;p&gt;This was a big initial test for us: we had to see if it was even &lt;em&gt;possible&lt;/em&gt; to use Gatsby for our site.&lt;/p&gt;

&lt;p&gt;After 20 years of blogging, we have over 17,000 posts published. We knew the builds would take a long time, but we had to find out if Gatsby could deal with such a massive amount of content. As you've probably figured, the test delivered good news: Gatsby works.&lt;/p&gt;

&lt;p&gt;A quick tip for other teams working with large sites: to make development a better experience, we used environment vars to prevent Gatsby from fetching &lt;em&gt;all&lt;/em&gt; of the site's posts in development. There's nothing quite like a 60 minute hot reload to slow progress.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasNextPage&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fetchPosts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;endCursor&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;From this point, we ran into some limitations with the WordPress source plugin. We couldn't get all the data we needed, so we moved to the &lt;a href="https://github.com/wp-graphql/wp-graphql"&gt;WordPress GraphQL plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We use Yoast to set our metadata for SEO, and had to ensure we were pulling in the correct information. We were able to do this with WordPress GraphQL. By doing it this way, the content team could still edit metadata the same way, and the data would still be dynamic and fetched on each build.&lt;/p&gt;

&lt;p&gt;During the build, we would have three or four people in the team working on parts of the new blog. In the past, if they wanted to get feedback they'd have to push to our staging server and make sure nobody was already using it.&lt;/p&gt;

&lt;p&gt;We found that Gatsby Cloud was a great solution to this issue. Now when someone pushes to a branch in GitHub, it creates a build in Gatsby Cloud along with a preview link. Our developers could share this link and get immediate testing and feedback much more effectively than before.&lt;/p&gt;

&lt;p&gt;This faster feedback cycle made it easy to have multiple people on the team working on the build and put an end to a major bottleneck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch Day Fun
&lt;/h2&gt;

&lt;p&gt;On the big day, we launched the new site and ran through our initial tests. The new blog was &lt;em&gt;flying&lt;/em&gt; — every page load felt instant.&lt;/p&gt;

&lt;p&gt;We ran into some problems on SitePoint Premium, which started running into slows and even crashes. The culprit was a new element on blog pages that pulled in the popular books people were currently reading. It would do this via a client-side API call, and it was too much for Premium to handle due to the amount of traffic we get on the blog side.&lt;/p&gt;

&lt;p&gt;We quickly added some page caching to the API to temporarily solve the issues. We realized we were doing this wrong — we should have been sourcing this data at build time, so that the popular books are already loaded when we serve the page to the user.&lt;/p&gt;

&lt;p&gt;This is the main mindset shift you need to make when using Gatsby: any data that you can get at build time should be fetched at build time. You should only use client-side API calls when you need live data.&lt;/p&gt;

&lt;p&gt;Once we'd re-written the API call to happen during the build, the first load of a blog page was even quicker — and Premium stopped crashing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Still Need to Solve
&lt;/h2&gt;

&lt;p&gt;While it's hard to overstate how much better our on-site experience is today, there are still a few pain points we need to solve.&lt;/p&gt;

&lt;p&gt;If a new article is published, or if content is updated — as it is multiple times per day — we need to re-run the Gatsby build before these changes show up.&lt;/p&gt;

&lt;p&gt;Our solution for that right now is a simple cron job that runs at pre-scheduled times over the course of a day. The long-term solution to this is to add a webhook to the WordPress publish and update button, so that a new build is triggered once pressed.&lt;/p&gt;

&lt;p&gt;We also need to get incremental builds running. Right now, the entire site needs to be rebuilt each time, and given our content archive, this can take a while. Gatsby just introduced incremental builds as we went live, and we're working on implementing this on our site. Once that's set up our builds will be much faster if the only thing that has changed is content.&lt;/p&gt;

&lt;p&gt;Our speed score is still not where we want it to be. While the site feels subjectively very fast, we are still not getting consistent scores in Lighthouse. We want to get both mobile and desktop into the green zone (scores of 90+) for optimal user experience and SEO.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would We Do It Again?
&lt;/h2&gt;

&lt;p&gt;A launch of this type would normally be a pretty nerve-wracking event, and take a lot of work from the team on launch day.&lt;/p&gt;

&lt;p&gt;With Gatsby, our launch was really easy. We just had to move WordPress onto a new domain, and point sitepoint.com at the Gatsby version of the site.&lt;/p&gt;

&lt;p&gt;Then we sat back and watched the numbers to see what happened to our traffic. Within a few days, the data was starting to come in and we were seeing a 15% increase in traffic. User engagement metrics were up across the board. And we hadn't even removed our ads yet (which, you may have noticed, we've since done).&lt;/p&gt;

&lt;p&gt;It's not hard to figure out why the effects were so immediate. We had better SEO running on static HTML and CSS pages, and massive speed improvements made possible by the move to Gatsby.&lt;/p&gt;

&lt;p&gt;Since we made the move, we've increased our Lighthouse speed scores from 6-15 on mobile to the 50-60 range, and from the 30s on desktop into the 70s. We wanted to ensure speed remained top of mind with this change, so we're using a great tool called &lt;a href="https://calibreapp.com/"&gt;Calibre&lt;/a&gt; that runs speed tests over a number of top pages each day and alerts us to the scores. We are using this tool to continue to improve our score, so I hope to have another article for you in three months when we get everything to stay in the 90+ range.&lt;/p&gt;

&lt;p&gt;The team loves working in Gatsby. The blog codebase was something that nobody wanted to work on. Now, everyone wants to take those cards thanks to the great developer experience.&lt;/p&gt;

&lt;p&gt;If you've been eyeing a move to Gatsby and wondering if it's ready for prime time, take our advice — it's worth the switch.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Build a Chrome Extension to Streamline Your Workflow</title>
      <dc:creator>SitePoint</dc:creator>
      <pubDate>Thu, 14 May 2020 07:56:06 +0000</pubDate>
      <link>https://dev.to/sitepoint/build-a-chrome-extension-to-streamline-your-workflow-f1j</link>
      <guid>https://dev.to/sitepoint/build-a-chrome-extension-to-streamline-your-workflow-f1j</guid>
      <description>&lt;p&gt;&lt;em&gt;By &lt;a href="https://www.sitepoint.com/author/jhibbard" rel="noopener noreferrer"&gt;James Hibbard&lt;/a&gt;. This article was originally published &lt;a href="https://www.sitepoint.com/build-a-chrome-extension/" rel="noopener noreferrer"&gt;on SitePoint&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When I began as a JavaScript editor at SitePoint, our submission process was something of a mess — articles coming from every direction in a variety of formats. So we decided to standardize things and settled on submission via GitHub in Markdown.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was a step forward, but we still needed to convert the Markdown into HTML for our WordPress back end. The powers that be at SitePoint HQ at the time had vetoed the installation of any WordPress plugins, which made us consider if we could accomplish this task with a browser extension. Luckily we could!&lt;/p&gt;

&lt;p&gt;In the following post, I'm going to demonstrate how you can build your own Chrome extension to add additional functionality to WordPress. I'll also introduce you to &lt;a href="https://github.com/jameshibbard/SP-Tools" rel="noopener noreferrer"&gt;SP-Tools&lt;/a&gt;, the extension we use at SitePoint to make life as an editor that little bit easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of a Chrome Extension
&lt;/h2&gt;

&lt;p&gt;Despite what you might think, building a Chrome extension isn't difficult. Let's start by looking at the various components.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Parts of the following section are borrowed from another tutorial I wrote about &lt;a href="https://www.sitepoint.com/build-vue-chrome-extension/" rel="noopener noreferrer"&gt;building a Chrome extension using Vue.js&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The core piece of any Chrome extension is a &lt;a href="https://developer.chrome.com/extensions/manifest" rel="noopener noreferrer"&gt;manifest file&lt;/a&gt;. This is in a JSON format and provides important information about an extension, such as its version, resources, or the permissions it requires.&lt;/p&gt;

&lt;p&gt;A manifest file won't do much on its own, so we can use a &lt;a href="https://developer.chrome.com/extensions/content_scripts" rel="noopener noreferrer"&gt;content script&lt;/a&gt; to add some functionality. Content scripts are files that "run in the context of web pages". That is to say, you specify the URL in your manifest file, then when your browser visits a page whose address matches the URL you specified, the content script is injected into the page and run.&lt;/p&gt;

&lt;p&gt;To demonstrate these concepts, let’s start by writing a Chrome extension to do something on the SitePoint main site.&lt;/p&gt;

&lt;p&gt;Make a new folder called &lt;code&gt;my-extension&lt;/code&gt; and two files, &lt;code&gt;manifest.json&lt;/code&gt; and &lt;code&gt;main.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-extension
&lt;span class="nb"&gt;cd &lt;/span&gt;my-extension
&lt;span class="nb"&gt;touch &lt;/span&gt;manifest.json main.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open up &lt;code&gt;manifest.json&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Extension&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manifest_version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content_scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matches&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*://*.sitepoint.com/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt; and &lt;code&gt;manifest_version&lt;/code&gt; are all required fields. The &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt; fields can be whatever you want; the &lt;a href="https://developer.chrome.com/extensions/manifestVersion" rel="noopener noreferrer"&gt;manifest version&lt;/a&gt; should be set to 2 (as of Chrome 18).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;content_scripts&lt;/code&gt; key allows us to register a content script (&lt;code&gt;main.js&lt;/code&gt;), which will be run whenever we visit SitePoint. Notice how we can use &lt;a href="https://developer.chrome.com/extensions/match_patterns" rel="noopener noreferrer"&gt;match patterns&lt;/a&gt; to specify parts of the URL, such as the protocol.&lt;/p&gt;

&lt;p&gt;Now let’s add the following code to &lt;code&gt;main.js&lt;/code&gt; to make the browser say hello whenever we visit SitePoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello there!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let’s install the extension. Open Chrome and enter &lt;code&gt;chrome://extensions/&lt;/code&gt; in the address bar. You should see a page displaying the extensions you’ve installed.&lt;/p&gt;

&lt;p&gt;As we want to install our extension from a file (and not the Chrome Web Store) we need to activate &lt;em&gt;Developer mode&lt;/em&gt; using the toggle in the top right-hand corner of the page. This should add an extra menu bar with the option &lt;em&gt;Load unpacked&lt;/em&gt;. Click this button and select the &lt;code&gt;my-extension&lt;/code&gt; folder you created previously. Click &lt;em&gt;Open&lt;/em&gt; and the extension will be installed.&lt;/p&gt;

&lt;p&gt;Now when you visit SitePoint, this will happen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdab1nmslvvntp.cloudfront.net%2Fwp-content%2Fuploads%2F2020%2F03%2F158348751501-chrome-ext.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdab1nmslvvntp.cloudfront.net%2Fwp-content%2Fuploads%2F2020%2F03%2F158348751501-chrome-ext.png" alt="Basic Extension"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations! You just made a Chrome extension.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background Scripts and Message Passing
&lt;/h3&gt;

&lt;p&gt;So, that dialogue box is pretty annoying right? To finish off this section, let's add a context menu entry to fire it off manually, instead of having it appear on every page load.&lt;/p&gt;

&lt;p&gt;This introduces us to another important component of Chrome extensions — &lt;a href="https://developer.chrome.com/extensions/background_pages" rel="noopener noreferrer"&gt;background scripts&lt;/a&gt;. These scripts can react to browser events (such as a user clicking a context menu entry) and they have full access to Chrome's APIs. However, they &lt;em&gt;don’t&lt;/em&gt; have access to the current page, and rely on message passing to communicate with content scripts.&lt;/p&gt;

&lt;p&gt;Update the manifest like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Extension&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manifest_version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;permissions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contextMenus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content_scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matches&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*://*.sitepoint.com/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;background.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;persistent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we’re requesting the &lt;code&gt;contextMenus&lt;/code&gt; permission, as we want to add something to the context menu, and that we’ve registered a non-persistent background script. Making the background script non persistent allows it to be unloaded when it’s not needed.&lt;/p&gt;

&lt;p&gt;Next, create a &lt;code&gt;background.js&lt;/code&gt; file and add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onInstalled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contextMenus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Say hi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;documentUrlPatterns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*://*.sitepoint.com/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contextMenus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onClicked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;menuItemId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We register the context menu entry when the extension is installed, then add an event listener to send a message to our content script whenever the entry is clicked.&lt;/p&gt;

&lt;p&gt;Change &lt;code&gt;main.js&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I greeted the user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we listen for a message from our background script. If it has a text of "greet", we then fire off the alert and send back a message informing the background script that we did what was requested.&lt;/p&gt;

&lt;p&gt;To try this out, head back to the extensions page (at &lt;code&gt;chrome://extensions/&lt;/code&gt;), then click the reload icon and reload any SitePoint page. When you right click, you should now see a context menu entry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdab1nmslvvntp.cloudfront.net%2Fwp-content%2Fuploads%2F2020%2F03%2F158349074502-chrome-ext.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdab1nmslvvntp.cloudfront.net%2Fwp-content%2Fuploads%2F2020%2F03%2F158349074502-chrome-ext.png" alt="Context menu entry for our extension"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancing WordPress' Functionality with a Chrome Extension
&lt;/h2&gt;

&lt;p&gt;Now that we’ve familiarized ourselves with the basic components of a Chrome extension, let's look at how we can make some additions to WordPress' functionality.&lt;/p&gt;

&lt;p&gt;To follow along with this section, you’ll need a &lt;a href="https://wordpress.org/support/article/how-to-install-wordpress/" rel="noopener noreferrer"&gt;working installation of WordPress&lt;/a&gt;. I installed mine locally. It’s running on an Apache server at &lt;code&gt;http://localhost/wp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The code for this extension can be found on &lt;a href="https://github.com/sitepoint-editors/wp-enhance" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a Markdown Converter
&lt;/h3&gt;

&lt;p&gt;Let's start by adding a Markdown converter to the WordPress editor. True to the experience on SitePoint, I'll be using the "classic" editor (achieved by installing the &lt;a href="https://wordpress.org/plugins/disable-gutenberg/" rel="noopener noreferrer"&gt;Disable Gutenberg plugin&lt;/a&gt;) and the &lt;em&gt;Text&lt;/em&gt; view.&lt;/p&gt;

&lt;p&gt;To begin, create the following folder structure for our new extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp-enhance
├── lib
│   ├── jquery.min.js
│   └── showdown.min.js
├── manifest.json
└── scripts
    └── main.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;wp-enhance
&lt;span class="nb"&gt;cd &lt;/span&gt;wp-enhance
&lt;span class="nb"&gt;mkdir &lt;/span&gt;lib scripts
&lt;span class="nb"&gt;touch &lt;/span&gt;lib/showdown.min.js lib/jquery.min.js
&lt;span class="nb"&gt;touch &lt;/span&gt;scripts/main.js
&lt;span class="nb"&gt;touch &lt;/span&gt;manifest.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we’ll be using the &lt;a href="https://github.com/showdownjs/showdown" rel="noopener noreferrer"&gt;Showdown markdown converter&lt;/a&gt; and jQuery (because I'm lazy).&lt;/p&gt;

&lt;p&gt;The first order of business is to grab the latest minified version of these libraries (&lt;a href="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.1/showdown.min.js" rel="noopener noreferrer"&gt;Showdown&lt;/a&gt; and &lt;a href="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" rel="noopener noreferrer"&gt;jQuery&lt;/a&gt;) and add the contents to the appropriate files.&lt;/p&gt;

&lt;p&gt;Next, add the following code to &lt;code&gt;manifest.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manifest_version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WP Enhance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A Chrome extension to enhance WordPress' functionality&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content_scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matches&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost/wp/wp-admin/post-new.php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lib/jquery.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lib/showdown.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts/main.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's nothing spectacular going on here. The extension is set to run when we visit &lt;code&gt;http://localhost/wp/wp-admin/post-new.php&lt;/code&gt;, and we're including the two libraries we just downloaded.&lt;/p&gt;

&lt;p&gt;Finally, in &lt;code&gt;scripts/main&lt;/code&gt; add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$editorToolbar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ed_toolbar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$mainTextArea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getShowdownConverter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;converter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;showdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Converter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Set any options here, for example to add table support&lt;/span&gt;
  &lt;span class="nx"&gt;converter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tables&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;converter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addMDButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mdConverter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getShowdownConverter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$convertButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;input /&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ed_button button button-small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Convert MD to HTML&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$mainTextArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mdConverter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;$mainTextArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;$editorToolbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$convertButton&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;addMDButton&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we’re creating a new button and appending it to the WordPress editor's toolbar. When it’s clicked, we’re calling Showdown's &lt;code&gt;makeHtml&lt;/code&gt; method, which we pass the contents of the content area. This returns us some HTML, which we then insert back into the editor.&lt;/p&gt;

&lt;p&gt;Load the extension and visit the new post page. You should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdab1nmslvvntp.cloudfront.net%2Fwp-content%2Fuploads%2F2020%2F03%2F158349764203-chrome-ext.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdab1nmslvvntp.cloudfront.net%2Fwp-content%2Fuploads%2F2020%2F03%2F158349764203-chrome-ext.png" alt="The Markdown Converter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm sure you'll agree, that's a reasonably impressive result in just a few lines of code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a Date Picker to the Publish Widget
&lt;/h3&gt;

&lt;p&gt;Next, we're going to enhance the publish widget with a datepicker. This will replace the series of drop-downs and input boxes that you normally see when you click the &lt;em&gt;Edit&lt;/em&gt; link next to the "Publish immediately" message in WordPress' &lt;em&gt;Publish&lt;/em&gt; widget.&lt;/p&gt;

&lt;p&gt;The first thing we’ll need to do is to download a datepicker. For this demo I'll be using &lt;a href="https://github.com/xdan/datetimepicker" rel="noopener noreferrer"&gt;this one&lt;/a&gt;. You can download the necessary files from &lt;a href="https://github.com/xdan/datetimepicker/releases/tag/2.5.20" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Unzip that file and place &lt;code&gt;build/jquery.datetimepicker.full.min.js&lt;/code&gt; into our &lt;code&gt;lib&lt;/code&gt; folder. Then create a new &lt;code&gt;css&lt;/code&gt; folder in the extension and place &lt;code&gt;build/jquery.datetimepicker.min.css&lt;/code&gt; into it.&lt;/p&gt;

&lt;p&gt;Our extension should now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp-enhance
├── css
│   └── jquery.datetimepicker.min.css
├── lib
│   ├── jquery.datetimepicker.full.min.js
│   ├── jquery.min.js
│   └── showdown.min.js
├── manifest.json
└── scripts
    └── main.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now include these files in the manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manifest_version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WP Enhance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A Chrome extension to enhance WordPress' functionality&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content_scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matches&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost/wp/wp-admin/post-new.php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lib/jquery.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lib/showdown.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lib/jquery.datetimepicker.full.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts/main.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;css/jquery.datetimepicker.min.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we need to alter our content script (&lt;code&gt;main.js&lt;/code&gt;) to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$editorToolbar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ed_toolbar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$mainTextArea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$timeStampDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#timestampdiv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$wpSchedulePostDropdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.timestamp-wrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;$datepicker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$dd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#jj&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$mm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#mm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$yyyy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#aa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$hh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#hh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$mn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#mn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getShowdownConverter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addMDButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addDatePicker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;$datepicker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;input /&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bandaid-datepicker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Date and time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;$datepicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetimepicker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;$timeStampDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$datepicker&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;addMDButton&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;$wpSchedulePostDropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;addDatePicker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;$datepicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateDateFields&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// String in format yyyy/mm/dd hh:mm&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dateString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;$yyyy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;$mm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;$dd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;$hh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;$mn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we’re doing is getting a reference to the input elements that WP uses to manage the time and date of the scheduled post. We’re then hiding these elements and initializing the datepicker. Whenever a user selects a date, the hidden field is updated and the post can be scheduled.&lt;/p&gt;

&lt;p&gt;Reload the extension, then refresh the WordPress new post page. What you have now should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdab1nmslvvntp.cloudfront.net%2Fwp-content%2Fuploads%2F2020%2F03%2F158350271304-chrome-ext.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdab1nmslvvntp.cloudfront.net%2Fwp-content%2Fuploads%2F2020%2F03%2F158350271304-chrome-ext.png" alt="The datepicker widget"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, an impressive result for not much code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Extension
&lt;/h2&gt;

&lt;p&gt;One of the things I noticed early on with our SP-Tools extension was that, when WordPress got updated, things would break. So, I got to thinking how I could test the extension and decided that some end-to-end tests with &lt;a href="https://nightwatchjs.org/" rel="noopener noreferrer"&gt;Nightwatch&lt;/a&gt; would make sense.&lt;/p&gt;

&lt;p&gt;In the following section, I'll demonstrate how we can test our extension in the same way.&lt;/p&gt;

&lt;p&gt;First, we'll need to generate a &lt;code&gt;package.json&lt;/code&gt; file. In the extension root, run &lt;code&gt;npm init -y&lt;/code&gt;. Next, let's install Nightwatch and the ChromeDriver as dev dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; nightwatch chromedriver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now make a &lt;code&gt;test&lt;/code&gt;directory and add a &lt;code&gt;nightwatch.config.js&lt;/code&gt; file, as well as a &lt;code&gt;wp.js&lt;/code&gt; file for our test code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir test
touch test&lt;/span&gt;/nightwatch.config.js &lt;span class="nb"&gt;test&lt;/span&gt;/wp.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;src_folders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output_folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;page_objects_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;custom_commands_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;custom_assertions_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;start_process&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9515&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;server_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules/.bin/chromedriver&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;log_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cli_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;test_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;desiredCapabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;browserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chromeOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load-extension=./&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--test-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is &lt;code&gt;'load-extension=./',&lt;/code&gt;, which tells Nightwatch to load our extension into the test browser.&lt;/p&gt;

&lt;p&gt;And add the following to &lt;code&gt;wp.js&lt;/code&gt; (replacing my login credentials with your own):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test WordPress Mods&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;browser&lt;/span&gt;
      &lt;span class="c1"&gt;// Login to WP Dashboard&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost/wp/wp-login.php&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#user_login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jim&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#user_pass&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#wp-submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// Go to New Post Page&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost/wp/wp-admin/post-new.php&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// Test MD &amp;gt; HTML conversion&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;## level 2 heading&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;### level 3 heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[value="MD"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valueContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h2 id="level2heading"&amp;gt;level 2 heading&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valueContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h3 id="level3heading"&amp;gt;level 3 heading&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// This is here so that you can take a look at the browser&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the tests using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node_modules/.bin/nightwatch &lt;span class="nt"&gt;--config&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;/nightwatch.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see an instance of the Chrome browser open and Nightwatch perform the tests we’ve specified. The result of the tests is output to the terminal.&lt;/p&gt;

&lt;p&gt;Hopefully Nightwatch's DSL is pretty self explanatory. You can read more about it in their &lt;a href="https://nightwatchjs.org/api/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. If you fancy a challenge, try adding tests for the datepicker.&lt;/p&gt;

&lt;p&gt;Note that I've hardcoded my credentials here. If you use this for anything other than demonstration purposes, it'll be a good idea to move these to a config file that’s not committed to GitHub.&lt;/p&gt;

&lt;p&gt;And don't forget you can find the code for everything I’ve demonstrated so far on &lt;a href="https://github.com/sitepoint-editors/wp-enhance" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notable Features of SitePoint's Chrome Extension
&lt;/h2&gt;

&lt;p&gt;As I'm sure you've realized, your mileage will vary regarding how useful you find such a browser extension. Most people will have (slightly) different needs and will be able to install WordPress plugins to solve most of the problems they encounter.&lt;/p&gt;

&lt;p&gt;Nonetheless, in this final section, I'd like to outline some of the features we have added to our &lt;a href="https://github.com/jameshibbard/SP-Tools" rel="noopener noreferrer"&gt;SP-Tools extension&lt;/a&gt; in the hope that they might inspire or even be useful for others.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A &lt;em&gt;Capitalize and Check&lt;/em&gt; button. This converts the post title to &lt;a href="http://www.grammar-monster.com/lessons/capital_letters_title_case.htm" rel="noopener noreferrer"&gt;title case&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  A headline analysis tool, which gives you a score out of 100 for your title and offers suggestions for improvements.&lt;/li&gt;
&lt;li&gt;  A &lt;em&gt;Capitalize Subheadings&lt;/em&gt; button, which checks the remaining headings in the article for title capitalization.&lt;/li&gt;
&lt;li&gt;  A &lt;em&gt;Copy Link&lt;/em&gt; button, which copies the post's current permalink to the clipboard.&lt;/li&gt;
&lt;li&gt;  A &lt;em&gt;Rebuild Link&lt;/em&gt; button, which rebuilds the post's permalink. This is useful, for example, when WordPress creates a permalink based on a draft heading which subsequently changes.&lt;/li&gt;
&lt;li&gt;  An extensible &lt;a href="http://www.urbandictionary.com/define.php?term=molly-guard" rel="noopener noreferrer"&gt;molly-guard&lt;/a&gt;, which performs a number of checks and disables/enables the publish button accordingly. Among other things, it checks for:

&lt;ul&gt;
&lt;li&gt;  a sensible post permalink&lt;/li&gt;
&lt;li&gt;  the presence of relative URLs in the editor pane&lt;/li&gt;
&lt;li&gt;  the presence of empty links in the editor pane&lt;/li&gt;
&lt;li&gt;  the presence of &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tags in the editor pane&lt;/li&gt;
&lt;li&gt;  the presence of &lt;code&gt;[special]&lt;/code&gt; shortcode tags in the excerpt&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  A &lt;em&gt;Copy Tags&lt;/em&gt; button, which gives you a comma-separated list of tags copied to the clipboard.&lt;/li&gt;

&lt;li&gt;  A &lt;em&gt;rel="sponsored"&lt;/em&gt; button, which toggles the &lt;code&gt;rel&lt;/code&gt; attribute of all links in a post as being &lt;code&gt;sponsored&lt;/code&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;If you'd like to check it out, you can &lt;a href="https://github.com/jameshibbard/SP-Tools" rel="noopener noreferrer"&gt;find our extension on GitHub&lt;/a&gt;. There are a few other goodies in there, such as context menu entries, some code to turn off the infinite scroll on our main site and, of course, tests with Nightwatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we’ve looked at the various components that make up a Chrome extension. I’ve demonstrated how we can build and test our own Chrome extension to enhance the basic functionality of a WordPress install. I’ve also introduced you to SP-Tools, SitePoint's own Chrome extension, which we use to make various editing tasks somewhat easier.&lt;/p&gt;

&lt;p&gt;If you find our extension useful, or adapt it to do anything else, I'd love to hear from you on &lt;a href="https://twitter.com/jchibbard" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>productivity</category>
      <category>wordpress</category>
    </item>
  </channel>
</rss>
