动机
在blogger中写博客的时候经常需要插入代码,通过在HTML编辑模式下为代码段添加
<code>...</code>
标签实现,一旦插入代码较多这个过程就显得比较繁琐了;因此考虑开发一个chrome的插件实现这个功能。主要的需求描述如下:选中需要添加代码HTML标签的代码段,右键菜单中显示需要添加代码段的模式,点击后将添加了HTML标签的代码段替换选中的原代码段。Demo
设计思路
预期实现的功能:为所选的可编辑内容弹出右键菜单,根据相应的标签选择替换所选内容。
为了实现这个功能,首先要对chrome插件的架构有一定的了解。下图给出了Chrome插件的插件的一个基本框架图。(由来源作者总结,感觉很清晰)
值得注意的是:Chrome的插件是有一个独立的运行环境的,可以是popup或者background,均可以是由HTML/CSS/JavaScript编写,用于呈现插件的显示或者功能。而此外Chrome还提供了content script的方式,用于在“匹配的”网页中注入(injection)content script脚本,从而获取或者操作页面上的DOM(Document Object Model)对象。注意:处于安全性的考虑,content script只对页面上的DOM对象有操作的权限,而对页面中的JavaScript脚本或其中出现的变量等均无访问的权限。
Message机制:目前看来,content script与插件之间是相互隔离的,无法交互;而实际上Chrome还提供了一个Message的方法为content script与插件之间提供了通信的方式。例如:可以在content script中发送消息(
sendMessage
),而在background script中注册该消息的监听(onMessage.addListener
),如此便可以实现content script通知background script的需求了;反之类似。来源: Chrome插件(Extensions)开发攻略 |
插件框架:有了以上对Chrome插件架构的基本了解以后,就可以设计本插件的框架图了,如下图所示。在extension的background页面调用了contextMenus这个API将插件功能添加到右键菜单中,为“selection”类型的内容(DOM对象)创建了该插件的右键菜单,并且在创建时绑定了“
onclick
”方法,在其中调用了sendMessage
方法,以此通知content script当前选择了需要替换的内容以及需要添加的标签类型。另一方面,在content script中,注册了onMessage
方法,从而可以监听background中发送的消息。在onMessage
方法中,实现了对“selection”内容的添加标签后替换的功能。实现
根据以上的设计思路,完成代码如下。(完整项目地址:CodeTag)
Chrome 插件下载地址: Code Tag
目前实现的功能是两个,分别可以添加Chrome 插件下载地址: Code Tag
manifest.json
{
"name": "Code Tag",
"description": "This extension helps add html tag for editable selected context",
"version": "0.2",
"permissions": [
"contextMenus"
],
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["addTag.js"]
}
],
"background": {
"scripts": [
"background.js"
]
},
"icons": {
"128": "icon.png"
},
"manifest_version": 2
}
background.js
// parent menu
var parent = chrome.contextMenus.create({
"title": "Code Tag",
"contexts": ["selection"]
});
// sub-menu for block style
chrome.contextMenus.create({
"title": "Block",
"parentId": parent,
"contexts": ["selection"],
"onclick": function (info, tab) {
if (info.editable) {
chrome.tabs.query({
"active": true,
"currentWindow": true
}, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, {
// codetag message, indicating block style
"codetag": "block"
});
});
}
}
});
// sub-menu for inline style
chrome.contextMenus.create({
"title": "Inline",
"parentId": parent,
"contexts": ["selection"],
"onclick": function (info, tab) {
if (info.editable) {
chrome.tabs.query({
"active": true,
"currentWindow": true
}, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, {
// codetag message, indicating inline style
"codetag": "inline"
});
});
}
}
});
addTag.js
(content script)// trancode "<" and ">" in code snippet into html coding
function transCode(text) {
var newStr;
newStr = text.replace(/</g, "<");
newStr = newStr.replace(/>/g, ">");
return newStr;
}
// register listener on message, fired when a sendMessage called in background
// in this function, selection is recognized and replaced with tag added context
// different tags are determined by the message sent from background
chrome.extension.onMessage.addListener(function (message, sender, callback) {
// get selection
var sel = window.getSelection();
var codeWithTag;
if (message.codetag == "block") {
codeWithTag = "<pre><code>" + transCode(sel.toString()) + "</code></pre>";
}
if (message.codetag == "inline") {
codeWithTag = "<code>" + transCode(sel.toString()) + "</code>";
}
var elem = document.activeElement;
var start = elem.selectionStart;
var end = elem.selectionEnd;
elem.value = elem.value.slice(0, start) + codeWithTag + elem.value.substr(end);
// Set cursor after selected text
elem.selectionStart = start + codeWithTag.length;
elem.selectionEnd = elem.selectionStart;
});
<code>...</code>
或者<pre><code>...</code></pre>
标签,从而实现Inline(嵌入行内)和Block(代码块)模式。(注:为了实现语法高亮,可以参考之前的文章:使用 highlight.js 高亮博文中的代码)遇到的问题
1.
起初,因为没有搞清楚Chrome插件的架构,导致踩了不少雷,其中就包括遇到
getSelection
无法获取预期的内容起初,因为没有搞清楚Chrome插件的架构,导致踩了不少雷,其中就包括遇到
getSelection
方法无法获取预期内容的问题;最初设计插件的时候只考虑了background,而未添加content script,这样一来实际上是无法通过JavaScript代码访问到页面内容的。而getSelection
是属于JavaScript中的方法。准确来说,在background中调用getSelection
也只是在background的页面中进行操作,也就是说如果访问DOM对象,例如document
,指代的是background页面,而我当时以为是当前的网页,这也就造成了getSelection
无法获取预期内容的现象。
2.
接上一个问题,由于无法获取
info.selectionText
将换行符替换为空格接上一个问题,由于无法获取
getSelection
的预期内容,那么后续的操作也就无从进行下去。改变思路找到Chrome的contextMenus
中的info.selectionText
属性,可以获取在菜单创建时所选择的文本。看上去可以解决需求。但是当选中的文本中出现换行符时,就不能如愿了,换行符被替换为空格。这个方案再次失效。其中selectionText
将换行符替换为空格的原因可能在于Chrome在菜单的显示中提供了“%s
”直接转换选中文本的方式,如果保留换行符那么在菜单显示的时候就不方便了。
3. 插件的Debug窗口无法看到content script
最终找到了 Chrome插件(Extensions)开发攻略 这篇文章,对Chrome插件的架构有了重新的认识,意识到可以通过content script实现需求。与此同时也了解了Chrome的Debug工具。但是在调试的过程中又遇到了新的问题。原本是在插件的背景页启动了Debug页面,而在其中的content script部分并未发现所写的content script代码,无从调试。原因在于content script代码是注入到匹配的页面中,而不是插件的背景页,所以要调试content script需要在网页中进行审查(Inspector),启动Debug。
4.
需求中是要对可编辑(
最终找到了 Chrome插件(Extensions)开发攻略 这篇文章,对Chrome插件的架构有了重新的认识,意识到可以通过content script实现需求。与此同时也了解了Chrome的Debug工具。但是在调试的过程中又遇到了新的问题。原本是在插件的背景页启动了Debug页面,而在其中的content script部分并未发现所写的content script代码,无从调试。原因在于content script代码是注入到匹配的页面中,而不是插件的背景页,所以要调试content script需要在网页中进行审查(Inspector),启动Debug。
4.
contextMenus
同时满足两个条件需求中是要对可编辑(
editable
)的选中内容(selection
)进行替换,contextMenus
可以根据点击发生的对象类型弹出相应的右键菜单,并且可以对不同的对象类型进行“OR”操作,但是并不支持“AND”操作,因此仅对于可编辑的选中内容右键弹出该插件菜单的操作是不能“直接”实现的。简单地变通方式可以如下:在contextMenus
中只关注“selection
”类型的对象,并且检查对象的“info.editable
”属性进行判断,如果为真则触发sendMessage
方法,否则不进行处理。参考
1. Chrome插件(Extensions)开发攻略
2. Chrome Extensions
3. Chrome Extension: how to capture selected text and send to a web service
4. Is there a flexible way to modify the contents of an editable element?
5. Is that possible calling content script method by context menu item in Chrome extension?
6. Debugging Content Scripts for Chrome Extension
7. Chrome Extension : Context menu on editable selected text only?
2. Chrome Extensions
3. Chrome Extension: how to capture selected text and send to a web service
4. Is there a flexible way to modify the contents of an editable element?
5. Is that possible calling content script method by context menu item in Chrome extension?
6. Debugging Content Scripts for Chrome Extension
7. Chrome Extension : Context menu on editable selected text only?
如果只是想简单实现:与正文不同的字体(等宽字体)、字号, 与正文的显示有所区分。
ReplyDelete可以利用blockquote的CSS自定义:https://zelikk.blogspot.com/2018/12/blogger-css-block-quote-code.html
更新了一下, 可以用JS把 blockquote 转化为 pre, 然后就可以用现成的各种代码美化方案了.
ReplyDeletehttps://zelikk.blogspot.com/2024/05/blogspot-code-prettify.html
update
ReplyDelete现在我添加了copy按钮
https://zelikk.blogspot.com/2024/06/blogspot-code-prism-hightlight-blockquote.html
https://zelikk.blogspot.com/2024/06/blogspot-blockquote-copy-button.html