65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (16投票s)

2016年4月29日

CPOL

5分钟阅读

viewsIcon

20228

downloadIcon

143

一个 Google Chrome 扩展,让 CodeProject 文章更具可读性。

最新代码版本,请查看此项目在 Github 上的地址。  

引言

如果你是那种会花时间定制 IDE 风格的人,并且喜欢阅读 codeproject.com 上的文章,那么这个扩展可能适合你。

安装

你可以从 Chrome 网上应用店 安装 Code Project 文章阅读器 Chrome 扩展。 

使用 扩展

安装扩展后,你会在地址栏的右侧看到扩展的图标  。由于此扩展仅适用于 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 之间的语言名称

语法高亮器需要了解语言才能正确执行高亮。当你在 codeproject 文章中插入代码块时,你可以从大约十几种不同的语言中选择。在 Prism 中,有超过一百种不同的语言。对于同一种语言,CodeProject 和 Prism 之间的名称也可能不同。例如,CodeProject 使用“cs”,而 Prism 使用“csharp”。为了处理这种差异,我们将使用以下代码来映射语言名称。
/**
 * 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>&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 将加载 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();
});  

结论

在本文中,我们介绍了 CodeProject Reader Google Chrome 扩展的功能和内部工作原理。我最初是作为一个有趣的实验项目来尝试 Chrome 扩展开发,但后来发现它足够有用,可以将其发布到 Chrome 网上应用店。我希望你也会觉得它很有用。
 

 

© . All rights reserved.