Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

CodeProject Reader Chrome Extension - Read CodeProject Articles in Style

4.79/5 (17 votes)
14 May 2016CPOL5 min read 19.7K   113  
A Google Chrome Extension that makes code project articles more readable.
For the latest version of the code, please check the project at Github.  

Introduction

If you are the type of person that will spend time to customize the styles of your IDE and you like to read articles on codeproject.com, this might be the extension for you.

Installation

You can install the Code Project Article Reader chrome extension from the chrome web store

Using the Extension

Once the extension is installed, you will see the extension's icon Image 1 showing on the right of the address bar. Since this extension is only applicable for codeproject.com articles, it will normally be disabled. It will become enabled when you visit a codeproject article/tips page.

On any codeproject article/tips page, click on extension's icon. The following popup will show.

Image 2

The Reading Theme drop down has 16 bootswatch themes you can choose from to customize the overall article styles. The Coding Theme drop down has 8 prism themes you can choose from to customize the syntax highlighting of the source code included in an article. The Layout drop down allows you to choose from 3 different fixed width layouts or fluid layout. Pressing the Read Article In Reader button will open a new tab where the article will be rendered in the selected themes. The theme selections are also saved to local storage so that preference will be persisted.

My personal favorite is the Readable bootswatch theme together with the Okaidia prism theme. This is what this article looks like in these themes.

Image 3

Using the code

A few articles here and here on codeproject already cover the basic of creating a chrome extension, so I am not going to repeat here. Instead, I will concentrate on explaining areas that are specific to this extension.

Enabling the extension's page action icon conditionally

This extension is only applicable on codeproject's article and tips page. We want the extension's page action icon to reflect that by making it only enabled on codeproject's articles and tips url. To do this, I used the Declarative Content API. It allows you to show your extension's page action depending on the URL of a web page and the CSS selectors its content matches, without needing to take a host permission or inject a content script. 

The following code is executed when the extension is installed or upgraded. Using the Declarative Content API, it declares the page action icon should only be shown when the current url matches a codeproject article/tips page.

JavaScript
// When the extension is installed or upgraded ...
chrome.runtime.onInstalled.addListener(function () {
  // Replace all rules ...
  chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
    // With a new rule ...
    chrome.declarativeContent.onPageChanged.addRules([
      {
        // That fires when a page's URL matches a codeproject article or tips url
        conditions: [
          new chrome.declarativeContent.PageStateMatcher({
             pageUrl: { urlMatches: 'codeproject.com/(Articles|Tips)' },
          })
        ],
        // And shows the extension's page action.
        actions: [new chrome.declarativeContent.ShowPageAction()]
      }
    ]);
  });
});

We also have to specify the declarativeContent permission in our manifest.json in order to use it.

JavaScript
  "permissions" : [
    "declarativeContent",

Note, the chrome extension api documentation mentions showing and hiding of a page action's icon. In my experience, the icon is only enabled or disabled.

Using the Bootswatch Themes

The reading themes are themes from bootswatch. Instead including all the css files with the extension, we will define the cssCdn for each of the available themes, and use the css from CDN at render time.

JavaScript
/**
 * define available bootswatch themes.
 */
var bootswatchThemes =
{
  "version": "3.3.6",
  "themes": [
    {
      "name": "Cerulean",
      "description": "A calm blue sky",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/cerulean/bootstrap.min.css"
    },
    {
      "name": "Cosmo",
      "description": "An ode to Metro",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/cosmo/bootstrap.min.css"
    },
    {
      "name": "Cyborg",
      "description": "Jet black and electric blue",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/cyborg/bootstrap.min.css"
    },
    {
      "name": "Darkly",
      "description": "Flatly in night mode",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/darkly/bootstrap.min.css"
    },
    {
      "name": "Flatly",
      "description": "Flat and modern",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/flatly/bootstrap.min.css"
    },
    {
      "name": "Journal",
      "description": "Crisp like a new sheet of paper",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/journal/bootstrap.min.css"
    },
    {
      "name": "Lumen",
      "description": "Light and shadow",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/lumen/bootstrap.min.css"
    },
    {
      "name": "Paper",
      "description": "Material is the metaphor",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/paper/bootstrap.min.css"
    },
    {
      "name": "Readable",
      "description": "Optimized for legibility",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/readable/bootstrap.min.css"
    },
    {
      "name": "Sandstone",
      "description": "A touch of warmth",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/sandstone/bootstrap.min.css"
    },
    {
      "name": "Simplex",
      "description": "Mini and minimalist",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/simplex/bootstrap.min.css"
    },
    {
      "name": "Slate",
      "description": "Shades of gunmetal gray",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/slate/bootstrap.min.css"
    },
    {
      "name": "Spacelab",
      "description": "Silvery and sleek",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/spacelab/bootstrap.min.css"
    },
    {
      "name": "Superhero",
      "description": "The brave and the blue",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/superhero/bootstrap.min.css"
    },
    {
      "name": "United",
      "description": "Ubuntu orange and unique font",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/united/bootstrap.min.css"
    },
    {
      "name": "Yeti",
      "description": "A friendly foundation",
      "cssCdn": "https://maxcdn.bootstrapcdn.com/bootswatch/latest/yeti/bootstrap.min.css"
    }
  ]
}

When we render an article, we will insert a stylesheet link element with the cssCdn path to apply the theme. Like this:

JavaScript
function applyBootstrapTheme() {
    var source = chrome.extension.getBackgroundPage().getUserBootswatchTheme().cssCdn;
    $('head').append('<link href="' + source + '" rel="stylesheet" type="text/css" />');
}

Using Prism

The coding themes are from Prism. Prism is a lightweight, extensible syntax highlighter. And it has some great looking themes. Like the bootswatch themes, instead including all the css files with the extension, we will define the cssCdn for each of the available themes. 

JavaScript
/**
 * define available prism themes
 */
var prismThemes = 
{
    "themes" : [
        {
            "name" : "Default",
            "cssCdn" : "https://cdnjs.cloudflare.com/ajax/libs/prism/1.4.1/themes/prism.min.css"
        },
        {
            "name" : "Coy",
            "cssCdn" : "https://cdnjs.cloudflare.com/ajax/libs/prism/1.4.1/themes/prism-coy.min.css"
        },
        {
            "name" : "Dark",
            "cssCdn" : "https://cdnjs.cloudflare.com/ajax/libs/prism/1.4.1/themes/prism-dark.min.css"
        },
        {           
            "name" : "Funky",
            "cssCdn" : "https://cdnjs.cloudflare.com/ajax/libs/prism/1.4.1/themes/prism-funky.min.css"
        },
        {           
            "name" : "Okaidia",
            "cssCdn" : "https://cdnjs.cloudflare.com/ajax/libs/prism/1.4.1/themes/prism-okaidia.min.css"
        },
        {           
            "name" : "Solarizedlight",
            "cssCdn" : "https://cdnjs.cloudflare.com/ajax/libs/prism/1.4.1/themes/prism-solarizedlight.min.css"
        },
        {           
            "name" : "Tomorrow",
            "cssCdn" : "https://cdnjs.cloudflare.com/ajax/libs/prism/1.4.1/themes/prism-tomorrow.min.css"
        },
        {           
            "name" : "Twilight",
            "cssCdn" : "https://cdnjs.cloudflare.com/ajax/libs/prism/1.4.1/themes/prism-twilight.min.css"
        }                                
    ]
};

We we render an article, we will insert a stylesheet link element with the cssCdn path. Like this:

JavaScript
function applyPrismjsTheme() {
    var source = chrome.extension.getBackgroundPage().getUserPrismTheme().cssCdn;    
    $('head').append('<link href="' + source + '" rel="stylesheet" type="text/css" />');    
}

We will also need to call Prism's highlightAll() method to apply the syntax highlighting.

JavaScript
Prism.highlightAll();

Mapping the Language name between CodeProject and Prism

Syntax Highlighter requires knowing the language in order to perform highlighting correctly. When you insert a code block in codeproject articles, you can choose from about a dozen different languages. In Prism, there are over a hundred different languages. The name can also be different between CodeProject and Prism for the same language. For example CodeProject uses "cs" while Prism uses "csharp". To handle this difference, we will use the following code to map the language names.
JavaScript
/**
 * Define a map that maps 
 *   the languages supported in codeproject http://www.codeproject.com/info/Submit.aspx
 * to 
 *   the languages supported in prismjs http://prismjs.com/#languages-list 
 */
var languages = {
    "asm"      : "clike",
    "aspnet"   : "aspnet",
    "bat"      : "bash",
    "cs"       : "csharp",
    "c++"      : "cpp",
    "css"      : "css",
    "dolphi"   : "clike",
    "F#"       : "fsharp",
    "html"     : "markup",
    "java"     : "java",
    "jscript"  : "javascript",
    "mc++"     : "cpp",
    "midl"     : "clike",
    "msil"     : "clike",
    "php"      : "php",
    "sql"      : "sql",
    "vbnet"    : "basic",
    "vb.net"   : "basic",
    "vbscript" : "basic",
    "xml"      : "markup",
};

/**
 * Given the language name used in codeproject, find the language name used in prismjs
*/
function getPrismLanguageName(name) {
    if (languages[name] != undefined) {
        return languages[name];
    }
    
    // use clike as the default language
    return "clike";
}

Capturing and Rending a CodeProject article

When user clicks on the Reader Article In Reader button, we will render the article user's viewing in a new tab using the selected themes. There are multiple steps involved.

First, we use the chrome.tabs.query api to get the current tab's url. The url is always the url for a codeproject article/tips page. Why? Because as mentioned earlier in this article, we use the Declarative Content API to enable the page action icon only on codeproject article/tips urls. The following code gets the current tab's url and saves it to the background script's articleUrl variable.

chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
    chrome.extension.getBackgroundPage().articleUrl = tabs[0].url;
});

At this point, we will also save the user's theme selections so user's preference will be remembered. The following code gets the selected theme names and calls the saveUserThemes method to save them to localStorage

JavaScript
var bootswatchSelectList = document.getElementById("bootswatch-theme-select");
var bootswatchThemeName = bootswatchSelectList.options[bootswatchSelectList.selectedIndex].value;
var prismSelectList = document.getElementById("prism-theme-select");
var prismThemeName = prismSelectList.options[prismSelectList.selectedIndex].value;
chrome.extension.getBackgroundPage().saveUserThemes(
    bootswatchThemeName, prismThemeName
)
JavaScript
/**
 * save user's preferred themes to local storage.
 */
function saveUserThemes(bootswatchThemeName, prismThemeName) {
  localStorage["UserBootstrapThemeName"] = bootswatchThemeName;
  localStorage["UserPrismThemeName"] = prismThemeName;  
}

Now that we have the article url and the themes, we will use the chrome.tabs.create api to open a new tab to show the render.html page where the rendering of the article will actually happen.

// show the article page in a new tab
chrome.tabs.create({ url: "render.html" });

Render.html has various placeholders for different elements of an article.

<div class="container" id="article-container" style="display:none;">
    <div class="jumbotron">
        <div class="container">
            <h2><span id="article-title">CodeProject Reader Chrome Extension</span><small></small></h2>
            <p class="lead" <span id="article-summary">
                </span>
            </p>
            <div class="lead small">
                By <span id="article-author"></span> <b>&nbsp;&middot;&nbsp;</b> 
                <span id="article-date">date</span> <b>&nbsp;&middot;&nbsp;</b> 
                <span id="article-rating"></span> (<span id="article-count"></span> votes)
            </div>
        </div>
    </div>

    <div id="article-body"></div>
</div>
<!-- article-container -->

Render.html will load the render.js script. The script will first download the content of the article url in a hidden div using jQuery load method. It will then parse out the various elements of the article and insert it into render.html placeholders.

 $( "#articledata" ).load( articleUrl + " div.article", function() {     
    // prepend http://www.codeproject.com to any img tags that uses relative paths (src attribute starts with slash /). 
    $('img[src^="/"]').each(function(index, element) {
        var newSrc = "http://www.codeproject.com" + $(this).attr("src");
        $(this).attr("src", newSrc);
    });

    // prepend http://www.codeproject.com to any a tags that uses relative paths (href attribute starts with slash /). 
    $('a[href^="/"]').each(function(index, element) {
        var newHref = "http://www.codeproject.com" + $(this).attr("href");
        $(this).attr("href", newHref);
    });        
    
    $("#article-title").html($("div.title").first().text());
    $("#article-summary").html($("div.summary").first().text());
    $("#article-author").html($("span.author").first().text());
    $("#article-date").html($("span.date").first().text());
    $("#article-rating").html($("span.rating").first().text() == "" ? "0.00" : $("span.rating").first().text());
    $("#article-count").html($("span.count").first().text() == "" ? "0" : $("span.count").first().text());
    $("#article-body").html($("#contentdiv").html());
    
    $("pre").each(function(index, element){
        var cpLanguageName = $(this).attr("lang");
        var prismLanguageName = getPrismLanguageName(cpLanguageName);
        $(this).wrapInner("<code class='language-" + prismLanguageName + "'></code>");
    });
    Prism.highlightAll();
    
    $("#loading-container").hide();
    $("#article-container").show();
});  

Conclusion

In this article, we introduce features and inner workings of the CodeProject Reader Google Chrome Extension. I started this as a fun project to experiment chrome extension development, but later found it was useful enough to put in on the chrome web store. I hope you will find it useful too.
 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)