This commit is contained in:
2026-04-27 20:25:00 +08:00
parent 14828597db
commit 45e377fbf2
6 changed files with 278 additions and 17 deletions

View File

@@ -0,0 +1,16 @@
{
"name": "defgov-bookmark-sync",
"version": "1.0",
"manifest_version": 3,
"permissions": ["bookmarks", "storage"],
"host_permissions": ["<all_urls>"],
"background": {
"service_worker": "background.js"
},
"browser_specific_settings": {
"gecko": {
"id": "bookmark-sync@example.com",
"strict_min_version": "109.0"
}
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "defgov-bookmark-sync",
"version": "0.0.0",
"private": true,
"dependencies": {
"typescript": "^6.0.3"
},
"devDependencies": {
"@types/chrome": "^0.1.40",
"@types/firefox-webext-browser": "^143.0.0",
"@types/node": "^25.6.0",
"@types/webextension-polyfill": "^0.12.5",
"webextension-polyfill": "^0.12.0"
}
}

View File

@@ -1,17 +0,0 @@
export class Server {
serverUrl: string;
constructor(serverUrl: string) {
this.serverUrl = serverUrl;
}
login(email: string, password: string) {
const result = false;
return result;
}
logout(email: string, password: string) {
const result = false;
return result;
}
}

View File

@@ -0,0 +1,73 @@
// background.ts
// 引入 polyfill 以统一 browser 和 chrome 命名空间
import browser from "webextension-polyfill";
// 定义书签节点的类型(可选,为了更严格的类型检查)
type BookmarkNode = browser.Bookmarks.BookmarkTreeNode;
/**
* 获取完整的书签树
* 使用 async/await 处理异步操作
*/
async function getBookmarkTree(): Promise<BookmarkNode[]> {
try {
// 在 Firefox 中browser.bookmarks 是原生的
// 在 Chrome/Edge 中polyfill 会自动将其映射为 Promise 版本的 chrome.bookmarks
const tree = await browser.bookmarks.getTree();
return tree;
} catch (error) {
console.error("获取书签树失败:", error);
throw error;
}
}
/**
* 获取最近添加的书签
*/
async function getRecentBookmarks(count: number = 10): Promise<BookmarkNode[]> {
return await browser.bookmarks.getRecent(count);
}
/**
* 将书签树转换为 JSON 字符串
*/
function serializeBookmarks(tree: BookmarkNode[]): string {
return JSON.stringify(tree, null, 2);
}
/**
* 主同步函数
*/
async function syncBookmarks() {
console.log("🚀 开始同步书签...");
try {
// 1. 获取数据
const tree = await getBookmarkTree();
// 2. 处理数据
const jsonString = serializeBookmarks(tree);
// 3. 模拟上传
console.log("✅ 书签数据准备就绪:", {
size: jsonString.length,
preview: jsonString.substring(0, 100) + "...",
});
// 这里可以添加 fetch 请求发送到你的服务器
// await fetch('YOUR_API_URL', { method: 'POST', body: jsonString ... })
} catch (err) {
console.error("❌ 同步过程发生错误:", err);
}
}
// 监听书签变化事件
browser.bookmarks.onChanged.addListener((id, changeInfo) => {
console.log(`🔔 书签变更通知: ID=${id}, 变更类型=${changeInfo}`);
// 防抖处理:实际项目中建议使用 lodash.debounce 避免频繁触发
syncBookmarks();
});
// 初始化执行
syncBookmarks();

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"types": ["chrome", "firefox-webext-browser"]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

157
pnpm-lock.yaml generated
View File

@@ -61,6 +61,28 @@ importers:
specifier: ^8.0.10
version: 8.0.10(@types/node@24.12.2)
packages/bookmark-sync:
dependencies:
typescript:
specifier: ^6.0.3
version: 6.0.3
devDependencies:
'@types/chrome':
specifier: ^0.1.40
version: 0.1.40
'@types/firefox-webext-browser':
specifier: ^143.0.0
version: 143.0.0
'@types/node':
specifier: ^25.6.0
version: 25.6.0
'@types/webextension-polyfill':
specifier: ^0.12.5
version: 0.12.5
webextension-polyfill:
specifier: ^0.12.0
version: 0.12.0
packages/ui:
dependencies:
'@base-ui/react':
@@ -113,6 +135,58 @@ importers:
specifier: ^4.5.4
version: 4.5.4(@types/node@25.6.0)(typescript@6.0.3)(vite@8.0.9(@types/node@25.6.0)(jiti@2.6.1))
packages/ui-web:
dependencies:
'@base-ui/react':
specifier: ^1.4.1
version: 1.4.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
react:
specifier: ^19
version: 19.2.5
react-dom:
specifier: ^19
version: 19.2.5(react@19.2.5)
tailwind-merge:
specifier: ^3.5.0
version: 3.5.0
tailwind-variants:
specifier: ^3.2.2
version: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.4)
tailwindcss:
specifier: ^4.2.4
version: 4.2.4
devDependencies:
'@tailwindcss/vite':
specifier: ^4.2.4
version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1))
'@types/node':
specifier: ^25.6.0
version: 25.6.0
'@types/react':
specifier: ^19.2.14
version: 19.2.14
'@types/react-dom':
specifier: ^19.2.3
version: 19.2.3(@types/react@19.2.14)
'@vitejs/plugin-react':
specifier: ^6.0.1
version: 6.0.1(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1))
glob:
specifier: ^13.0.6
version: 13.0.6
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@25.6.0)(typescript@6.0.3)
typescript:
specifier: ^6.0.3
version: 6.0.3
vite:
specifier: ^8.0.9
version: 8.0.10(@types/node@25.6.0)(jiti@2.6.1)
vite-plugin-dts:
specifier: ^4.5.4
version: 4.5.4(@types/node@25.6.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1))
packages:
'@babel/code-frame@7.29.0':
@@ -738,12 +812,27 @@ packages:
'@types/argparse@1.0.38':
resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
'@types/chrome@0.1.40':
resolution: {integrity: sha512-UnfyRAe8ORu9HSuTH0EqyOEUin3JrWW9Nl/gDXezNfTUrfIoxw+WRZgKOxGz0t5BnjbfXBnS2eCYfW2PxH1wcA==}
'@types/esrecurse@4.3.1':
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/filesystem@0.0.36':
resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==}
'@types/filewriter@0.0.33':
resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==}
'@types/firefox-webext-browser@143.0.0':
resolution: {integrity: sha512-865dYKMOP0CllFyHmgXV4IQgVL51OSQQCwSoihQ17EwugePKFSAZRc0EI+y7Ly4q7j5KyURlA7LgRpFieO4JOw==}
'@types/har-format@1.2.16':
resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -761,6 +850,9 @@ packages:
'@types/react@19.2.14':
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
'@types/webextension-polyfill@0.12.5':
resolution: {integrity: sha512-uKSAv6LgcVdINmxXMKBuVIcg/2m5JZugoZO8x20g7j2bXJkPIl/lVGQcDlbV+aXAiTyXT2RA5U5mI4IGCDMQeg==}
'@typescript-eslint/eslint-plugin@8.59.0':
resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1781,6 +1873,9 @@ packages:
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
webextension-polyfill@0.12.0:
resolution: {integrity: sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==}
which@2.0.1:
resolution: {integrity: sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==}
engines: {node: '>= 8'}
@@ -2338,6 +2433,13 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.4
'@tailwindcss/oxide-win32-x64-msvc': 4.2.4
'@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1))':
dependencies:
'@tailwindcss/node': 4.2.4
'@tailwindcss/oxide': 4.2.4
tailwindcss: 4.2.4
vite: 8.0.10(@types/node@25.6.0)(jiti@2.6.1)
'@tailwindcss/vite@4.2.4(vite@8.0.9(@types/node@25.6.0)(jiti@2.6.1))':
dependencies:
'@tailwindcss/node': 4.2.4
@@ -2378,10 +2480,25 @@ snapshots:
'@types/argparse@1.0.38': {}
'@types/chrome@0.1.40':
dependencies:
'@types/filesystem': 0.0.36
'@types/har-format': 1.2.16
'@types/esrecurse@4.3.1': {}
'@types/estree@1.0.8': {}
'@types/filesystem@0.0.36':
dependencies:
'@types/filewriter': 0.0.33
'@types/filewriter@0.0.33': {}
'@types/firefox-webext-browser@143.0.0': {}
'@types/har-format@1.2.16': {}
'@types/json-schema@7.0.15': {}
'@types/node@24.12.2':
@@ -2400,6 +2517,8 @@ snapshots:
dependencies:
csstype: 3.2.3
'@types/webextension-polyfill@0.12.5': {}
'@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1)(typescript@6.0.3))(eslint@10.2.1)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -2496,6 +2615,11 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-rc.7
vite: 8.0.10(@types/node@24.12.2)
'@vitejs/plugin-react@6.0.1(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.7
vite: 8.0.10(@types/node@25.6.0)(jiti@2.6.1)
'@vitejs/plugin-react@6.0.1(vite@8.0.9(@types/node@25.6.0)(jiti@2.6.1))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.7
@@ -3288,6 +3412,25 @@ snapshots:
v8-compile-cache-lib@3.0.1: {}
vite-plugin-dts@4.5.4(@types/node@25.6.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1)):
dependencies:
'@microsoft/api-extractor': 7.58.7(@types/node@25.6.0)
'@rollup/pluginutils': 5.3.0
'@volar/typescript': 2.4.28
'@vue/language-core': 2.2.0(typescript@6.0.3)
compare-versions: 6.1.1
debug: 4.4.3
kolorist: 1.8.0
local-pkg: 1.1.2
magic-string: 0.30.21
typescript: 6.0.3
optionalDependencies:
vite: 8.0.10(@types/node@25.6.0)(jiti@2.6.1)
transitivePeerDependencies:
- '@types/node'
- rollup
- supports-color
vite-plugin-dts@4.5.4(@types/node@25.6.0)(typescript@6.0.3)(vite@8.0.9(@types/node@25.6.0)(jiti@2.6.1)):
dependencies:
'@microsoft/api-extractor': 7.58.7(@types/node@25.6.0)
@@ -3318,6 +3461,18 @@ snapshots:
'@types/node': 24.12.2
fsevents: 2.3.3
vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
postcss: 8.5.10
rolldown: 1.0.0-rc.17
tinyglobby: 0.2.16
optionalDependencies:
'@types/node': 25.6.0
fsevents: 2.3.3
jiti: 2.6.1
vite@8.0.9(@types/node@25.6.0)(jiti@2.6.1):
dependencies:
lightningcss: 1.32.0
@@ -3332,6 +3487,8 @@ snapshots:
vscode-uri@3.1.0: {}
webextension-polyfill@0.12.0: {}
which@2.0.1:
dependencies:
isexe: 2.0.0