Let’s breakdown the ‘Save in Ember’ browser extension

I’ve written this as an accompaniment to the Save in Ember Chrome extension I’ve just put up on Github.

I’ll walk through the basics of Chrome extensions then show you the bits and bobs that make up this one.

It’s functionality is fairly straight forward: when you’re on https://dribbble.com, it’ll add a button in the likes panel that — when clicked — will send the shot straight to your Ember library. This is made possible by one of Ember’s URL schemes: emberapp:///import?url={{URL}}.

Before and After of the Dribbble UI

You’ll still find parts of this post informative, even if you don’t have a copy of Ember. Should you want to try it out however, there’s a 14 day free trial of Ember available on our site.

Before we crack on, it’s worth noting that Ember already has a far more fully featured extension available for both Safari and Chrome. This was just a bit of fun.

The Basics

In Chrome, browser extensions boil down to a collection of HTML, CSS and Javascript files (images are also allowed). In essence, they’re web pages that have access to a bunch of APIs allowing them to interact with the browser’s content. How simple or complex your extensions are is really down to your own imagination.

The Chrome team have done an excellent job documenting everything you could possibly want to know when building your extension. Said docs are available on their Developer site, here.

The Structure

As it only contains a few files, I kept the folder structure of Save in Ember fairly flat. You can organise things into seperate folders if you wish, just remember to update the paths in manifest.json when you do (more on that later).

send-to-ember/
    inject.css
    inject.js
    jquery.min.js
    manifest.json
    img/
        icon_ember.png
        icon_ember@2x.png
        icon_ember_hover.png
        icon_ember_hover@2x.png
  • manifest.json is included in every Chrome extension out there. It’s there to give Chrome information about the extension (version, author, et cetera) as well as to tell it what it requires by way of permissions.
  • inject.css and inject.js contain the styles and scripts we’re going to insert into the webpage once it’s loaded.
  • jquery.min.js is simply a copy of jQuery.
  • img/ just contains the Ember icon we’re going to use on the button.

The Breakdown

Now for the good stuff. Let’s go through each of these files and see what’s going on.

manifest.json

{
  "name": "Save in Ember",
  "version": "1.0",
  "manifest_version": 2,
  "description": "A simple Chrome extension that adds a `Save in Ember` button next to Dribbble shots.",
  "homepage_url": "http://casualnotebook.com/lets-make-a-simple-browser-extension-for-chrome",
  "content_scripts": [{
    "css": [
      "inject.css"
    ],
    "js": [
      "inject.js",
      "jquery.min.js"
    ],
    "matches": [
      "https://dribbble.com/*"
    ]
  }],
  "web_accessible_resources": [
    "img/*.png"
  ]
}
  • name, version, description and homepage_url are all fairly self explanatory.
  • manifest_version tells Chrome the version of the manifest file format your extensions requires. Version 1 has been considered deprecated since Chrome 18.
  • content_scripts are files run within the webpage. Be forewarned: they do not have access to a majority of Chrome’s APIs.
  • web_accessible_resources tells Chrome the paths of files we want to have access to within the webpage. They can either be direct or use a wildcard.

Note: name, version, and manifest_version are the only required parameters in a manifest.json file.

inject.css

.meta-ember {
  background-image: url('chrome-extension://__MSG_@@extension_id__/img/icon_ember.png');
  background-position: 12px 9px !important;
  cursor: pointer;
}

.meta-ember:hover {
  background-image: url('chrome-extension://__MSG_@@extension_id__/img/icon_ember_hover.png');
  color: #444 !important;
}

@media
(-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
  .meta-ember {
    background-image: url('chrome-extension://__MSG_@@extension_id__/img/icon_ember@2x.png');
    background-size: 15px 16px;
    background-position: 12px 9px !important;
  }

  .meta-ember:hover {
    background-image: url('chrome-extension://__MSG_@@extension_id__/img/icon_ember_hover@2x.png');
    background-size: 15px 16px;
  }
}

We’re going to re-use some of the CSS Dribbble already loads in it’s own stylesheet so there isn’t much to see here. We’re just telling our button which icon it should load on normal and high resolution screens. Make sure that any assets you want to use in injected CSS are declared under web_accessible_resources in your manifest file.

Where this differs from the CSS you’re used to is in the background-image path. Links to packaged resources within injected files need to have chrome-extension://__MSG_@@extension_id__ prepended to them. If you simply used background-image: url('/img/icon_ember.png'); then your injected CSS would look for an icon_ember.png file in an img directory on the website’s own server, not within your extension.

inject.js

chrome.extension.sendMessage({}, function(response)
{
  var readyStateCheckInterval = setInterval(function()
  {
    if (document.readyState === "complete")
    {
      clearInterval(readyStateCheckInterval);
      // Page is done loading
      ///////////////////////////////////////

      // Get the image link
      var image_link  = $(".single-img img").attr("src");

      // Insert the button
      $(".meta-act:nth-child(2)").after("<div class='meta-act meta-act-full'><span class='meta-act-link meta-ember' id='send-to-ember'>Save in Ember</span></div>");

      // Open the tab in the background
      document.getElementById("send-to-ember").addEventListener("click", generateLink, false);

      function generateLink()
      {
        var a = document.createElement("a");
        a.href = "emberapp:///import?url=" + image_link;

        var evt = document.createEvent("MouseEvents");
        evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, true, false, false, false, 0, null);
        a.dispatchEvent(evt);
      }

    }
  }, 10);
});
  • Lines 1 through 7 make sure our code only runs once the page has loaded.
  • Line 12 get’s us the URL of the image we’re going to be sending to Ember by looking within the .single-img div each shot is wrapped in.
  • Line 15 inserts our button in the likes panel. We’re re-using Dribble’s own classes here to save us from having to do style updates.
  • Instead of directly linking to the image with something like <a href="emberapp:///import?url={{URL}}">Save in Ember</a> which would open a new tab, we should open it in the background so that the Dribbble-browsing experience isn’t interrupted. The button we inserted on line 15 is actually just a trigger with fake link properties we setup in the CSS file (:hover). Once it’s clicked, lines 18 through 28 generate a link with the URL scheme and image URL then fake an Option+Click on it to open it in the background

What’s next?

If you want to tinker with this, you can download it over on Github. Put it in a folder, open the extensions tab in Chrome’s preferences, make sure “Developer mode” is checked, then drag the folder onto the window.

If you just want to use it then you’ll also find a link to the latest version, bundled up and ready to go, in the repo’s description.

How to copy an RGB value in Photoshop

The following is a result of me scratching my own itch, hopefully you’ll find it useful too. Before we go any further, it’s worth noting that this is a Mac-only solution. If you’re running Windows and want to re-write what I’ve done in VBScript or Javascript (or are just curious) then carry on. If you just came for the how-to then this isn’t for you, sorry.

Unlike web designers who work primarily with hex values when it comes to colours, interface designers work mostly with RGB(A). Unluckily for us, whilst Photoshop provides an easy way to copy a shape’s hex value to the clipboard it doesn’t offer an RGB(A) equivalent. If you are unfamiliar with this feature of Photoshop, here’s the quickest way to use it:

  • Go to Edit ➜ Keyboard Shortcuts
  • Under Shortcuts for select Panel Menus
  • Toggle Color
  • Scroll to Copy Color’s Hex Code and assign it a keyboard shortcut

I’m constantly grabbing colour values from PSDs, but after a while opening the content options of each of the layers whose values I need, then typing out said values manually into an RGB([red], [green], [blue]) string gets pretty tedious, pretty quickly.

A few weeks ago I wrote a script that does a fairly good job replicating Photoshop’s native “Copy Color’s Hex Code” except of course, it’s “Copy’s Color’s RGB Code”. It’s been a good time-saving addition to my workflow, despite it’s limitations due to one of Photoshop’s AppleScript restrictions: as far as I can tell from Adobe’s documentation, there’s no way to get any form of colour value from the currently selected layer in Photoshop (although I’m more than happy to be proven wrong if you know of a way). As it doesn’t use the selected layer for the colour, it wouldn’t make sense for it to use the alpha channel value of the currently selected layer either, so if your layer has one you’ll need to add that manually. Hence the use of “RGB” instead of “RGB(A)” above.

What that means is that once setup, the only difference between the native functionality and our scripted one is that you’ll have to use the colour picker to select the colour you want to copy instead of selecting the layer. Whilst not ideal, as I said, it’s still saved me a lot of time.

Ok, now let’s take a look at the script:

-- Convert the current foreground colour in
-- Photoshop to RGB then copy to clipboard
-- by Elliot Jackson | http://casualnotebook.com
tell application "Adobe Photoshop CC 2014"
  set fgc to convert color foreground color to RGB
  set redInt to red of fgc as integer
  set greenInt to green of fgc as integer
  set blueInt to blue of fgc as integer
  set the clipboard to "RGB(" & (redInt) & ", " & (greenInt) & ", " & (blueInt) & ")" as text
end tell

And here’s a step-by-step of what it’s actually doing:

  1. Convert the current foreground colour in Photoshop to red, green and blue values.
  2. Independently set the values of the red, green and blue channels to the nearest integer.
  3. Add each value to it’s appropriate slot in the “RGB($red, $green, $blue)” string.
  4. Copy the whole lot to the clipboard.

Note: The values are put into an RGB() string because of how we have things setup here at Realmac. If you’re interested, here are the macros that we use:

#define RGBA(r, g, b, a) [NSColor colorWithDeviceRed:(r / 255.0) green:(g / 255.0) blue:(b / 255.0) alpha:a]
#define RGB(r, g, b) RGBA(r, g, b, 1.0)

If you are using a version of Photoshop other than CC 2014, you’ll want to change tell application "Adobe Photoshop CC 2014" to whichever release you have.

Let’s get this setup so you can actually use it. There are two parts to this, adding the script as a Service and then giving it a keyboard shortcut in Photoshop.

Setting the script as a Service:

  1. Launch “Automator.app” from your Applications folder.
  2. Select New Document ➜ Service.
  3. In the search box (top left), type “Run AppleScript”
  4. You should have 1 result come up, if you don’t then make sure “Library” is selected on the left.
  5. Drag it over to the main area, delete any code that’s already in the input then paste in the script above.
  6. File ➜ Save and give it a relevant name, this will be used later.

Triggering it with a Keyboard Shortcut

  1. Launch System Preferences then go to Keyboard.
  2. Go to Shortcuts ➜ Services.
  3. Scroll down until you see it. It will be named the same as what saved it as in Automator.
  4. Select it then click Add Shortcut. Make sure this doesn’t conflict with any of your existing Photoshop ones.

Restart Photoshop and there we go, you should now be able to use it. If you want to run it manually, go to Photoshop ➜ Services ➜ [ whatever you named it ] in the menu bar. Granted it takes a few minutes to setup but in the long run, it’s worth it.

Hopefully you’ll find this as useful as I have.

Note: This is a re-post from my old (and retired) blog.