欢迎来到飞鸟慕鱼博客,开始您的技术之旅!
当前位置: 首页知识笔记正文

chrome 书签 同步,chrome浏览器书签同步

终极管理员 知识笔记 125阅读
说在前面

平时大家都是怎么管理自己的浏览器书签数据的呢有没有过公司和家里的电脑浏览器书签不同步的情况有没有过电脑突然坏了但书签数据没有导出导致书签数据丢失了解决这些问题的方法有很多我选择自己写个chrome插件来做书签同步。

实现方案 通过 gitee 来做存取

建一个私有仓库来保存自己的书签目录信息需要同步的时候再获取 gitee 仓库的书签目录到本地。这样不用自己写服务端对数据进行存储减少了很多不必要的开发工作。

实现步骤 一、准备工作 1、新建 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浏览器书签

导入书签前我们需要先清除一下当前浏览器的书签通过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/选择加载已解压的扩展程序

选择插件目录导入即可

导入成功后就可以看到下面这个插件了

可以勾选上下面这个勾选后插件就会显示在导航栏上

3、补充gitee仓库信息数据

导入插件后我们点击导航栏的插件图标可以看到这样一个面板其中有四个数据需要我们填写

获取 token

进入到giteeAPI文档进行授权获取到返回填写即可具体步骤如下

仓库所属空间地址(owner)

就是个人主页的一个空间地址如下图

仓库路径(repo)

前面新建仓库的路径仓库名如下图

书签文件路径(filePath)

新建用于保存书签数据的文件想保存多份不同的数据的话可以多件几个不同的文件分别进行存储同步的时候选择对应的目录即可如下图

将对应信息填写上之后我们就可以开始进行同步操作了

4、同步方式 1覆盖保存

使用当前浏览器书签数据覆盖保存到gitee仓库中。

2合并保存

将当前浏览器书签数据与gitee仓库中的书签数据合并好再进行保存。

3覆盖获取

使用gitee仓库中的书签数据覆盖掉本地的书签数据。

4合并获取

将gitee仓库中的书签数据和本地的书签数据合并后再覆盖掉本地的书签数据。

5合并规则

同一层级并且同名的目录我们会将其子节点合并到同一目录下同一层级下我们会根据 书签名 书签url 对该层级的书签进行去重。

源码 1、gitee

gitee 地址 2、公众号

关注公众号『前端也能这么有趣』发送 chrome插件即可获取源码。

说在后面

这里是 JYeontu现在是一名前端工程师有空会刷刷算法题平时喜欢打羽毛球 平时也喜欢写些东西既为自己记录 也希望可以对大家有那么一丢丢的帮助写的不好望多多谅解 写错的地方望指出定会认真改进 偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章有兴趣的也可以关注下。在此谢谢大家的支持我们下文再见 。

标签:
声明:无特别说明,转载请标明本文来源!