CodeProject 阅读器 Chrome 扩展 - 时尚地阅读 CodeProject 文章






4.79/5 (16投票s)
一个 Google Chrome 扩展,让 CodeProject 文章更具可读性。
引言
如果你是那种会花时间定制 IDE 风格的人,并且喜欢阅读 codeproject.com 上的文章,那么这个扩展可能适合你。
安装
你可以从 Chrome 网上应用店 安装 Code Project 文章阅读器 Chrome 扩展。
使用 扩展
安装扩展后,你会在地址栏的右侧看到扩展的图标  。由于此扩展仅适用于 codeproject.com 文章,因此它通常会处于禁用状态。当你访问 codeproject 文章/技巧页面时,它会启用。
 。由于此扩展仅适用于 codeproject.com 文章,因此它通常会处于禁用状态。当你访问 codeproject 文章/技巧页面时,它会启用。
在任何 codeproject 文章/技巧页面上,点击扩展图标。会出现以下弹出窗口。

阅读主题下拉菜单有 16 Bootswatch 主题可供选择,用于自定义整体文章样式。代码主题下拉菜单有 8 Prism 主题可供选择,用于自定义文章中源代码的语法高亮。布局下拉菜单允许你选择 3 种不同的固定宽度布局或流式布局。按下阅读文章按钮将在新标签页中打开文章,并以选定的主题呈现。主题选择也会保存到本地存储,以便首选项能够持久化。
我个人最喜欢的是Readable Bootswatch 主题和 Okaidia Prism 主题。这就是这篇文章在这两个主题下的样子。

使用代码
codeproject 上的一些文章 这里 和 这里 已经涵盖了创建 Chrome 扩展的基础知识,所以我在这里不再赘述。相反,我将专注于解释此扩展特有的领域。
有条件地启用扩展的页面操作图标
此扩展仅适用于 codeproject 的文章和技巧页面。我们希望扩展的页面操作图标能够反映这一点,使其仅在 codeproject 的文章和技巧 URL 上启用。为此,我使用了声明式内容 API。它允许你根据网页的 URL 和其内容匹配的 CSS 选择器来显示扩展的页面操作,而无需获取主机权限或注入内容脚本。
以下代码在扩展安装或升级时执行。使用声明式内容 API,它声明只有当当前 URL 匹配 codeproject 文章/技巧页面时,页面操作图标才应显示。
// 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()]
      }
    ]);
  });
});
我们还需要在 manifest.json 中指定 declarativeContent 权限才能使用它。
  "permissions" : [
    "declarativeContent",
注意,Chrome 扩展 API 文档提到了页面操作图标的显示和隐藏。根据我的经验,图标只是启用或禁用。
使用 Bootswatch 主题
阅读主题来自 Bootswatch。我们不会将所有 CSS 文件都包含在扩展中,而是为每个可用主题定义 cssCdn,并在渲染时使用 CDN 中的 CSS。
/**
 * define available bootswatch themes.
 */
var bootswatchThemes =
{
  "version": "3.3.6",
  "themes": [
    {
      "name": "Cerulean",
      "description": "A calm blue sky",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/cerulean/bootstrap.min.css"
    },
    {
      "name": "Cosmo",
      "description": "An ode to Metro",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/cosmo/bootstrap.min.css"
    },
    {
      "name": "Cyborg",
      "description": "Jet black and electric blue",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/cyborg/bootstrap.min.css"
    },
    {
      "name": "Darkly",
      "description": "Flatly in night mode",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/darkly/bootstrap.min.css"
    },
    {
      "name": "Flatly",
      "description": "Flat and modern",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/flatly/bootstrap.min.css"
    },
    {
      "name": "Journal",
      "description": "Crisp like a new sheet of paper",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/journal/bootstrap.min.css"
    },
    {
      "name": "Lumen",
      "description": "Light and shadow",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/lumen/bootstrap.min.css"
    },
    {
      "name": "Paper",
      "description": "Material is the metaphor",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/paper/bootstrap.min.css"
    },
    {
      "name": "Readable",
      "description": "Optimized for legibility",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/readable/bootstrap.min.css"
    },
    {
      "name": "Sandstone",
      "description": "A touch of warmth",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/sandstone/bootstrap.min.css"
    },
    {
      "name": "Simplex",
      "description": "Mini and minimalist",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/simplex/bootstrap.min.css"
    },
    {
      "name": "Slate",
      "description": "Shades of gunmetal gray",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/slate/bootstrap.min.css"
    },
    {
      "name": "Spacelab",
      "description": "Silvery and sleek",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/spacelab/bootstrap.min.css"
    },
    {
      "name": "Superhero",
      "description": "The brave and the blue",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/superhero/bootstrap.min.css"
    },
    {
      "name": "United",
      "description": "Ubuntu orange and unique font",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/united/bootstrap.min.css"
    },
    {
      "name": "Yeti",
      "description": "A friendly foundation",
      "cssCdn": "https://maxcdn.bootstrap.ac.cn/bootswatch/latest/yeti/bootstrap.min.css"
    }
  ]
}
渲染文章时,我们将插入一个样式表链接元素,其中包含 cssCdn 路径以应用主题。就像这样
function applyBootstrapTheme() {
    var source = chrome.extension.getBackgroundPage().getUserBootswatchTheme().cssCdn;
    $('head').append('<link href="' + source + '" rel="stylesheet" type="text/css" />');
}
使用 Prism
代码主题来自 Prism。Prism 是一个轻量级、可扩展的语法高亮器。它有一些非常漂亮的主题。与 Bootswatch 主题一样,我们不会将所有 CSS 文件都包含在扩展中,而是为每个可用主题定义 cssCdn。
/**
 * 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"
        }                                
    ]
};
渲染文章时,我们将插入一个样式表链接元素,其中包含 cssCdn 路径。就像这样
function applyPrismjsTheme() {
    var source = chrome.extension.getBackgroundPage().getUserPrismTheme().cssCdn;    
    $('head').append('<link href="' + source + '" rel="stylesheet" type="text/css" />');    
}
我们还需要调用 Prism 的 highlightAll() 方法来应用语法高亮。
Prism.highlightAll();
映射 CodeProject 和 Prism 之间的语言名称
/**
 * Define a map that maps 
 *   the languages supported in codeproject https://codeproject.org.cn/info/Submit.aspx
 * to 
 *   the languages supported in prismjs https://prism.npmjs.net.cn/#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";
}
捕获并渲染 CodeProject 文章
当用户点击“阅读文章”按钮时,我们将在新标签页中渲染用户正在查看的文章,并使用选定的主题。这涉及多个步骤。
首先,我们使用 chrome.tabs.query API 获取当前标签页的 URL。URL 始终是 codeproject 文章/技巧页面的 URL。为什么?因为正如本文前面提到的,我们使用声明式内容 API 仅在 codeproject 文章/技巧 URL 上启用页面操作图标。以下代码获取当前标签页的 URL 并将其保存到后台脚本的 articleUrl 变量中。
chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
    chrome.extension.getBackgroundPage().articleUrl = tabs[0].url;
});
此时,我们还将保存用户的选定主题,以便记住用户的首选项。以下代码获取选定的主题名称并调用 saveUserThemes 方法将它们保存到 localStorage。
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
)
/**
 * save user's preferred themes to local storage.
 */
function saveUserThemes(bootswatchThemeName, prismThemeName) {
  localStorage["UserBootstrapThemeName"] = bootswatchThemeName;
  localStorage["UserPrismThemeName"] = prismThemeName;  
}
现在我们有了文章 URL 和主题,我们将使用 chrome.tabs.create API 打开一个新标签页来显示 render.html 页面,文章的渲染将在那里实际发生。
// show the article page in a new tab
chrome.tabs.create({ url: "render.html" });
Render.html 包含文章不同元素的各种占位符。
<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> · </b> <span id="article-date">date</span> <b> · </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 将加载 render.js 脚本。该脚本将首先使用 jQuery load 方法在隐藏的 div 中下载文章 URL 的内容。然后,它将解析文章的各种元素,并将其插入到 render.html 的占位符中。
 $( "#articledata" ).load( articleUrl + " div.article", function() {     
    // prepend https://codeproject.org.cn to any img tags that uses relative paths (src attribute starts with slash /). 
    $('img[src^="/"]').each(function(index, element) {
        var newSrc = "https://codeproject.org.cn" + $(this).attr("src");
        $(this).attr("src", newSrc);
    });
    // prepend https://codeproject.org.cn to any a tags that uses relative paths (href attribute starts with slash /). 
    $('a[href^="/"]').each(function(index, element) {
        var newHref = "https://codeproject.org.cn" + $(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();
});  
结论


