chrome 书签 同步,chrome浏览器书签同步
终极管理员 知识笔记 125阅读
平时大家都是怎么管理自己的浏览器书签数据的呢有没有过公司和家里的电脑浏览器书签不同步的情况有没有过电脑突然坏了但书签数据没有导出导致书签数据丢失了解决这些问题的方法有很多我选择自己写个chrome插件来做书签同步。
实现方案 通过 gitee 来做存取建一个私有仓库来保存自己的书签目录信息需要同步的时候再获取 gitee 仓库的书签目录到本地。这样不用自己写服务端对数据进行存储减少了很多不必要的开发工作。

直接在gitee上新建仓库即可。
我们不想要书签信息公开所以选择勾选上私有

创建完的初始仓库是这样的
我们再新增一个目录用于存放和书签相关的文件
在该目录下新增一个文件用于保存书签导出的数据
完成前面的准备工作新建完 gitee 仓库之后我们便可以正式开始进行插件的编写了。
1、插件模板 安装依赖jyeontu
npm i -g jyeontu
获取模板 jyeontu create
生成模板 根据提示输入相关信息即可
2、giteeAPI我们可以通过 giteeAPI 来对 gitee 仓库进行操作下面是 giteeAPI 的操作文档
获取gitee指定文件的内容
我们可以通过下面代码来获取到gitee指定仓库指定文件的内容
async function fetchFileContent(apiUrl, accessToken) { const response await fetch(apiUrl, { headers: { Authorization: token accessToken, }, }); const fileData await response.json(); return fileData.content;}export async function getFile(gitInfo) { const accessToken gitInfo.token; const apiUrl gitInfo.owner / gitInfo.repo /contents/ gitInfo.filePath; const fileContent await fetchFileContent(apiUrl, accessToken); const decodedContent atob(fileContent); // 解码Base64编码的文件内容 const decoder new TextDecoder(); const decodedData decoder.decode( new Uint8Array([...decodedContent].map((char) > char.charCodeAt(0))) ); return JSON.parse(decodedData);}
修改指定文件的内容数据 我们需要先获取到文件拿到文件的sha
值后面通过sha
来对文件进行编辑操作。btoa
函数只能处理Latin1字符范围内的字符串对超出Latin1字符范围的字符串进行Base64编码我们需要进行以下操作使用TextEncoder
对象来将字符串转换为字节数组然后再进行Base64编码。
async function fetchFileContent(apiUrl, accessToken) { const response await fetch(apiUrl, { headers: { Authorization: token accessToken, }, }); const fileData await response.json(); return fileData.content;}async function getDecodedContent(content) { const decodedContent atob(content); // 解码Base64编码的文件内容 const decoder new TextDecoder(); const decodedData decoder.decode( new Uint8Array([...decodedContent].map((char) > char.charCodeAt(0))) ); return JSON.parse(decodedData);}async function putFileContent(apiUrl, accessToken, encodedContent, sha) { const commitData { access_token: accessToken, content: encodedContent, message: Modified file, sha: sha, }; const putResponse await fetch(apiUrl, { method: PUT, headers: { Content-Type: application/json, Authorization: token accessToken, }, body: JSON.stringify(commitData), }); if (putResponse.ok) { console.log(File modified successfully.); } else { console.error(Failed to modify file.); }}export async function modifyFile(gitInfo, modifiedContent) { const accessToken gitInfo.token; const apiUrl gitInfo.owner / gitInfo.repo /contents/ gitInfo.filePath; try { const fileContent await fetchFileContent(apiUrl, accessToken); const content await getDecodedContent(fileContent); modifiedContent mergeBookmarks(content, modifiedContent); modifiedContent JSON.stringify(modifiedContent); const encoder new TextEncoder(); const data encoder.encode(modifiedContent); const encodedContent btoa( String.fromCharCode.apply(null, new Uint8Array(data)) ); await putFileContent(apiUrl, accessToken, encodedContent, fileContent.sha); } catch (error) { console.error(An error occurred:, error); }}
3、indexDb存取 我们不希望每次打开都需要去重新填写gitee仓库的相关信息所以这里我们使用indexDb
来对gitee仓库的相关信息做一个保存。
export class IndexedDB { constructor(databaseName, storeName) { this.databaseName databaseName; this.storeName storeName; this.db null; } open() { return new Promise((resolve, reject) > { const request window.indexedDB.open(this.databaseName); request.onerror () > { reject(new Error(Failed to open database)); }; request.onsuccess () > { this.db request.result; resolve(); }; request.onupgradeneeded (event) > { this.db event.target.result; if (!this.db.objectStoreNames.contains(this.storeName)) { this.db.createObjectStore(this.storeName, { keyPath: id, autoIncrement: true, }); } }; }); } createDatabase() { return new Promise((resolve, reject) > { const request window.indexedDB.open(this.databaseName); request.onerror () > { reject(new Error(Failed to create database)); }; request.onsuccess () > { this.db request.result; this.db.close(); resolve(); }; request.onupgradeneeded (event) > { this.db event.target.result; if (!this.db.objectStoreNames.contains(this.storeName)) { this.db.createObjectStore(this.storeName, { keyPath: id, autoIncrement: true, }); } this.db.close(); resolve(); }; }); } close() { if (this.db) { this.db.close(); this.db null; } } add(data) { return new Promise((resolve, reject) > { const transaction this.db.transaction(this.storeName, readwrite); const objectStore transaction.objectStore(this.storeName); const request objectStore.add(data); request.onsuccess () > { resolve(request.result); }; request.onerror () > { reject(new Error(Failed to add data)); }; }); } getAll() { return new Promise((resolve, reject) > { const transaction this.db.transaction(this.storeName, readonly); const objectStore transaction.objectStore(this.storeName); const request objectStore.getAll(); request.onsuccess () > { resolve(request.result); }; request.onerror () > { reject(new Error(Failed to get data)); }; }); } getById(id) { return new Promise((resolve, reject) > { const transaction this.db.transaction(this.storeName, readonly); const objectStore transaction.objectStore(this.storeName); const request objectStore.get(id); request.onsuccess () > { resolve(request.result); }; request.onerror () > { reject(new Error(Failed to get data)); }; }); } delete(id) { return new Promise((resolve, reject) > { const transaction this.db.transaction(this.storeName, readwrite); const objectStore transaction.objectStore(this.storeName); const request objectStore.delete(id); request.onsuccess () > { resolve(); }; request.onerror () > { reject(new Error(Failed to delete data)); }; }); } update(id, newData) { return new Promise((resolve, reject) > { const transaction this.db.transaction(this.storeName, readwrite); const objectStore transaction.objectStore(this.storeName); const getRequest objectStore.get(id); getRequest.onsuccess () > { const oldData getRequest.result; if (!oldData) { const addRequest objectStore.add({ ...newData, id }); addRequest.onsuccess () > { resolve({ ...newData, id }); }; addRequest.onerror () > { reject(new Error(Failed to add data)); }; } else { const mergedData { ...oldData, ...newData }; const putRequest objectStore.put(mergedData); putRequest.onsuccess () > { resolve(mergedData); }; putRequest.onerror () > { reject(new Error(Failed to update data)); }; } }; getRequest.onerror () > { reject(new Error(Failed to get data)); }; }); }}
4、书签存取 获取chrome书签 要获取 Chrome 浏览器的书签目录我们可以使用 Chrome 浏览器提供的 API——chrome.bookmarks。下面是一个示例代码演示如何使用chrome.bookmarks
API 获取 Chrome 浏览器的书签目录
export const getBookmarks () > { return new Promise((resolve) > { chrome.bookmarks.getTree(function (bookmarkTreeNodes) { resolve(bookmarkTreeNodes); }); });};
在上述代码中我们首先使用chrome.bookmarks.getTree()
方法获取 Chrome 浏览器的书签目录树。
请注意要使用chrome.bookmarks
API你需要在你的 Chrome 插件中声明bookmarks
权限。具体来说在插件清单文件manifest.json中添加以下内容
{ manifest_version: 2, name: 你的插件名称, version: 1.0, permissions: [ bookmarks ], background: { scripts: [ bg.js ] }}
在上述代码中我们在permissions
字段中声明了bookmarks
权限以便我们可以使用chrome.bookmarks
API。同时在background
字段中指定了一个后台脚本bg.js以便我们在后台执行上述代码。
导入书签前我们需要先清除一下当前浏览器的书签通过chrome.bookmarks.removeTree
可以删除书签节点。
export function removeBookmarks(bookmarkTreeNodes) { // 遍历书签树删除所有的书签 function traverseBookmarks(bookmarkNodes) { for (const node of bookmarkNodes) { if (node.children) { traverseBookmarks(node.children); } // 删除书签节点 chrome.bookmarks.removeTree(node.id); } } traverseBookmarks(bookmarkTreeNodes);}
导入书签 使用chrome.bookmarks.create
来新建书签。
export function importBookmarks(bookmarkTreeNodes) { // 遍历书签树 function traverseBookmarks(bookmarkNodes, parentId) { for (const node of bookmarkNodes) { // 如果节点是文件夹 if (node.children) { // 创建一个新的文件夹节点 chrome.bookmarks.create( { parentId: parentId, title: node.title, }, function (newFolderNode) { // 递归遍历子节点 traverseBookmarks(node.children, newFolderNode.id); } ); } // 如果节点是书签 else { // 创建一个新的书签节点 chrome.bookmarks.create({ parentId: parentId, title: node.title, url: node.url, }); } } } // 从根节点开始遍历书签树 traverseBookmarks(bookmarkTreeNodes[0].children, 1);}
插件使用 1、插件下载 直接到gitee上下载源码即可
源码地址 2、导入插件
书签同步插件的目录如下
下载完后打开浏览器扩展程序管理页面chrome://extensions/选择加载已解压的扩展程序
选择插件目录导入即可
导入成功后就可以看到下面这个插件了
可以勾选上下面这个勾选后插件就会显示在导航栏上
导入插件后我们点击导航栏的插件图标可以看到这样一个面板其中有四个数据需要我们填写
进入到giteeAPI文档进行授权获取到返回填写即可具体步骤如下
owner
) 就是个人主页的一个空间地址如下图
repo
) 前面新建仓库的路径仓库名如下图
filePath
) 新建用于保存书签数据的文件想保存多份不同的数据的话可以多件几个不同的文件分别进行存储同步的时候选择对应的目录即可如下图
将对应信息填写上之后我们就可以开始进行同步操作了
使用当前浏览器书签数据覆盖保存到gitee仓库中。
2合并保存将当前浏览器书签数据与gitee仓库中的书签数据合并好再进行保存。
3覆盖获取使用gitee仓库中的书签数据覆盖掉本地的书签数据。
4合并获取将gitee仓库中的书签数据和本地的书签数据合并后再覆盖掉本地的书签数据。
5合并规则同一层级并且同名的目录我们会将其子节点合并到同一目录下同一层级下我们会根据 书签名 书签url 对该层级的书签进行去重。
源码 1、giteegitee 地址 2、公众号
关注公众号『前端也能这么有趣』发送 chrome插件
即可获取源码。
这里是 JYeontu现在是一名前端工程师有空会刷刷算法题平时喜欢打羽毛球 平时也喜欢写些东西既为自己记录 也希望可以对大家有那么一丢丢的帮助写的不好望多多谅解 写错的地方望指出定会认真改进 偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章有兴趣的也可以关注下。在此谢谢大家的支持我们下文再见 。