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






4.79/5 (16投票s)
一个 Google Chrome 扩展,让 CodeProject 文章更具可读性。
引言
如果你是那种会花时间定制 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 之间的语言名称
/**
* 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(); });
结论