首发于微信公众号《前端成长记》,写于 2019.10.18 导读 有句老话说的好,好记性不如烂笔头。人生中,总有那么些东西你愿去执笔写下。 本文旨在把整个开发的过程和遇到的问题及解决方案记录下来,希望能够给你带来些许帮助。 安装和源码 安装和源码 背景 在 《干货!从0开始,0成本搭建个人动态博客》 中,已经完成了动态博客的搭建。接下来,将围绕该博客,开发对应的 Chrome拓展,方便使用。 上手开发 本文不需要前期准备,直接跟我做就好了 功能拆分 这里主要分为几个大的功能点: 内容菜单导航,方便快速进入到博客的指定菜单页 地址栏搜索,根据内容可直接在地址栏出现匹配结果的文章 新文章推送,如果有文章更新则自动推送 Ⅰ.必要知识介绍 Chrome 拓展插件 实际上是由 HTML/CSS/JS/图片 等资源组成的一个 .crx 的拓展包,解压出来即可得到真正内容。 Chrome 拓展插件 对项目结构没有要求,只需要在开发根目录下有一个 mainfest.json 即可。 进入 Chrome 拓展程序 页面,打开 开发者模式 开始我们的开发之路。 Ⅱ.基础配置开发 首先,新建一个 src 目录作为插件的文件目录,然后新建一个 mainfest.json 文件,文件内容如下: // mainfest.json { // 插件名称 "name": "McChen", // 插件版本号 "version": "0.0.1", // 插件描述 "description": "Chrome Extension for McChen.", // 插件主页 "homepage_url": "https://chenjiahao.xyz", // 版本必须指定为2 "manifest_version": 2 } 然后打开 Chrome 拓展程序页面,点击 加载已解压的拓展程序 按钮,选择上面新建的 src 文件,将会看到如下两处变化: code-img1 code-img2 你会发现你的拓展插件已经添加到右上角了,点击右键时出现的第一行为 name ,点击跳转链接为 homepage_url 。 接下来我们为我们的拓展插件添加图标,在 src 中新建一个名为 icon.png 的图标,然后修改 mainfest.json 文件: // mainfest.json { ... "icons": { "16": "icon.png", "32": "icon.png", "48": "icon.png", "128": "icon.png" } ... } 点击插件开发的更新图标,我们可以看到图标已经加上了: code-img3 code-img4 这里会发现,右上角的图标为什么是置灰的呢?这里就需要聊到 browser_action 和 page_action 。[参考文档] browser_action :如果你想让图标一直可见,那么配置该项 page_action :如果你不想让图标一直可见,那么配置该项 为了让图标一直可见,我们来修改下 mainfest.json : { ... "browser_action": { "default_icon": "icon.png", "default_title": "McChen" }, ... } 此时再次更新查看效果: code-img5 到这里,基础的配置开发已经完成了,接下来就是功能部分。 Ⅲ.内容菜单导航开发 [参考文档] 内容导航菜单我用在两个地方:鼠标点击右上角图标的 Popup 和网页中按鼠标右键出现的菜单。 先看看鼠标点击右上角图标 Popup 的,给 mainfest.json 增加 default_popup 就是 popup 展示的页面内容了。 { ... "browser_action": { "default_icon": "icon.png", "default_title": "McChen", "default_popup": "popup.html" }, ... } 新建一个 popup.html 文件,内容如下: McChen 主页 博客 标签 友链 关于 留言 搜索 我们更新后来看看效果,点击右上角图标将会看到如下的内容弹窗: code-img6 下一步,我们来实现在网页中按鼠标右键出现的菜单。 首先,你必须要配置对应的权限才能使用这个 API ,还需要配置修改 mainfest.json 内容: [权限参考文档] ... "permissions": [ "contextMenus" ] ... 接下来,需要通过 API 调用去创建对应的菜单,这里需要用到常驻在后台运行的 js 才行,所以还需要修改 mainfest.json 文件: ... "background": { "scripts": [ "background.js" ] }, ... 然后我们新建一个 backgroud.js 文件,文件内容如下: [参考文档] chrome.contextMenus.create({ id: 'McChen', title: 'McChen', contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'home', title: '主页', parentId: 'McChen', // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'archives', title: '博客', parentId: 'McChen', // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'labels', title: '标签', parentId: 'McChen', // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'links', title: '友链', parentId: 'McChen', // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'about', title: '关于', parentId: 'McChen', // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'board', title: '留言', parentId: 'McChen', // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); chrome.contextMenus.create({ id: 'search', title: '搜索', parentId: 'McChen', // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单 contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'page_action'] }); // 监听菜单点击事件 chrome.contextMenus.onClicked.addListener(function (info, tab) { if (info.menuItemId === 'home') { chrome.tabs.create({url: 'https://chenjiahao.xyz'}); } else { chrome.tabs.create({url: 'https://chenjiahao.xyz/blog/#/' + info.menuItemId}); } }) 更新后,点击鼠标右键将查看到如下内容: code-img7 至此,内容菜单导航功能已全部完成。 Ⅳ.地址栏搜索开发 [参考文档] 地址栏搜索主要是通过 Omnibox 来实现的,我们首先需要设置关键字,在这里我设置成 'mc' ,修改 mainfest.json 文件: ... { "omnibox": { "keyword" : "mc" } } ... 更新后,我们在地址栏输入 mc 按 Tab 或者 Space 键可看到如下内容: code-img8 接下来我们进行接口开发,由于需要进行接口调用,所以需要配置允许请求的地址,修改 mainfest.json 文件: ... { "permissions": [ "contextMenus", // 允许请求全部https "https://*/" ], } ... 然后修改 background.js 文件内容: ... let timer = ''; chrome.omnibox.onInputChanged.addListener((text, suggest) => { if (timer) { clearTimeout(timer) timer = '' } else { timer = setTimeout(() => { if (text.length > 1) { const xhr = new XMLHttpRequest(); xhr.open("POST", "https://api.artfe.club/transfer/github", true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') xhr.onreadystatechange = function () { if (xhr.readyState === 4) { const list = JSON.parse(xhr.responseText).data.search.nodes; if (list.length) { suggest(list.map(_ => ({content: 'ISSUE_NUMBER:' + _.number, description: '文章 - ' + _.title}))) } else { suggest([ {content: 'none', description: '无相关结果'} ]) } } }; xhr.send('query=' + query); } else { suggest([ {content: 'none', description: '查询中,请稍后...'} ]) } }, 300) } }); // 当选中建议内容时触发 chrome.omnibox.onInputEntered.addListener((text) => { if (text.startsWith('ISSUE_NUMBER:')) { const number = text.substr(13) chrome.tabs.query({active: true, currentWindow: true}, function (tabs) { if (tabs.length) { const tabId = tabs[0].id; const url = 'https://chenjiahao.xyz/blog/#/archives/' + number; chrome.tabs.update(tabId, {url: url}); } }); } }); ... 这里有几个地方需要注意一下: onInputChanged 这方法触发频率高,和正常开发一样,需要做一次函数防抖,要不然请求频率会特别高。 这里面不允许写 Promise ,所以我使用的 XMLHttpRequest suggest 中 content 和 description 字段都不允许为空,但是在事件回调里需要识别,所以我这里特意增加了一个前缀 ISSUE_NUMBER: 更新后,在地址栏输入 mc 按 Tab 后,输入 干货 ,将会看到如下内容: code-img9 至此,地址栏搜索功能已全部完成。 Ⅴ.新文章推送开发 [存储参考文档] [推送参考文档] 新文章推送功能,首先我们需要知道之前的最新文章是哪篇,才能做到精准推送,所以这里需要用到 Storage ,也就是存储功能。存下最新文章的 ID ,轮询最新文章,如果有更新,则存下最新文章的 ID 并且调用推送的 API 。所以,我们需要先增加权限配置,修改 mainfest.json 文件: ... "permissions": [ "storage", "contextMenus", "notifications", "https://*/" ], ... 然后修改 'background.js' 文件内容: ... getLatestNumber(); chrome.storage.sync.get({LATEST_TIMER: 0}, function (items) { if (items.LATEST_TIMER) { clearInterval(items.LATEST_TIMER) } const LATEST_TIMER = setInterval(() => { getLatestNumber() }, 1000 * 60 * 60 *24) chrome.storage.sync.set({LATEST_TIMER: LATEST_TIMER}) }); function getLatestNumber () { const query = `query { repository(owner: "ChenJiaH", name: "blog") { issues(orderBy: {field: CREATED_AT, direction: DESC}, labels: null, first: 1, after: null) { nodes { title number } } } }`; const xhr = new XMLHttpRequest(); xhr.open("POST", "https://api.artfe.club/transfer/github", true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { const list = JSON.parse(xhr.responseText).data.repository.issues.nodes; if (list.length) { const title = list[0].title; const ISSUE_NUMBER = list[0].number; chrome.storage.sync.get({ISSUE_NUMBER: 0}, function(items) { if (items.ISSUE_NUMBER !== ISSUE_NUMBER) { chrome.storage.sync.set({ISSUE_NUMBER: ISSUE_NUMBER}, function() { chrome.notifications.create('McChen', { type: 'basic', iconUrl: 'icon.png', title: '新文章发布通知', message: title }); chrome.notifications.onClicked.addListener(function (notificationId) { if (notificationId === 'McChen') { chrome.tabs.create({url: 'https://chenjiahao.xyz/blog/#/archives/' + ISSUE_NUMBER}); } }) }); } }); } } }; xhr.send('query=' + query); } ... 注意:由于是后台常驻,所以需要增加轮询来判断是否有更新,我这里设置的是一天一次 更新后,第一次我们会看到浏览器右下角会有推送消息如下: code-img10 至此,新文章推送功能也已经开发完成了。 打包发布 在拓展程序页面点击打包扩展程序,选择 src 作为根目录打包即可。 将会生成 src.crx 和 src.pem 两个文件, .crx 文件就是你提交到拓展商店的资源, .pem 文件是私钥,下次进行打包更新时需要使用。 由于打包需要 5$ ,所以我这里就不做演示了,需要的可以自行尝试,[发布地址] 结尾 一个基于动态博客的 Chrome 拓展插件 就开发完了,欢迎下载使用。 如有疑问或不对之处,欢迎留言。 (完) 本文为原创文章,可能会更新知识点及修正错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验 如果能给您带去些许帮助,欢迎 ⭐️star 或 ✏️ fork (转载请注明出处:https://chenjiahao.xyz)https://www.cnblogs.com/McChen/p/11703137.html