From abfcec734be95935525da902df44f15c402aa4f0 Mon Sep 17 00:00:00 2001 From: "yinjun.chen1" Date: Wed, 16 Sep 2026 17:31:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=AF=BC=E5=85=A5=E4=B9=A6?= =?UTF-8?q?=E7=AD=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _locales/en/messages.json | 28 +++++++ _locales/zh_CN/messages.json | 28 +++++++ manifest.json | 2 +- popup.css | 82 ++++++++++++++++++++ popup.html | 24 +++++- popup.js | 140 +++++++++++++++++++++++++++++++++++ 6 files changed, 302 insertions(+), 2 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b6ce368..d2106a8 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -798,5 +798,33 @@ "logGetLockStatusFail": { "message": "Failed to get lock status", "description": "Log - failed to get lock status" + }, + "importBookmarkBtnTitle": { + "message": "Import from bookmarks", + "description": "Bookmark import button title" + }, + "bookmarkImportTitle": { + "message": "Import from bookmarks", + "description": "Bookmark import dialog title" + }, + "bookmarkSelectAll": { + "message": "Select all", + "description": "Bookmark select all" + }, + "bookmarkSearchPlaceholder": { + "message": "Search bookmarks", + "description": "Bookmark search placeholder" + }, + "bookmarkImportBtn": { + "message": "Import selected", + "description": "Bookmark import confirm button" + }, + "bookmarkLoadFail": { + "message": "Failed to load bookmarks", + "description": "Toast - bookmark load failed" + }, + "toastBookmarkImported": { + "message": "Imported $1, skipped $2", + "description": "Toast - bookmark import result, $1 imported, $2 skipped" } } diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 511e19c..aaa85d3 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -798,5 +798,33 @@ "logGetLockStatusFail": { "message": "获取锁状态失败", "description": "日志 - 获取锁状态失败" + }, + "importBookmarkBtnTitle": { + "message": "从书签导入", + "description": "书签导入按钮 title" + }, + "bookmarkImportTitle": { + "message": "从书签导入", + "description": "书签导入弹窗标题" + }, + "bookmarkSelectAll": { + "message": "全选", + "description": "书签全选" + }, + "bookmarkSearchPlaceholder": { + "message": "搜索书签", + "description": "书签搜索框 placeholder" + }, + "bookmarkImportBtn": { + "message": "导入选中", + "description": "书签导入确认按钮" + }, + "bookmarkLoadFail": { + "message": "书签加载失败", + "description": "toast - 书签加载失败" + }, + "toastBookmarkImported": { + "message": "已导入 $1 条,跳过 $2 条", + "description": "toast - 书签导入结果,$1 导入数,$2 跳过数" } } diff --git a/manifest.json b/manifest.json index ff3d9ab..828ce82 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "version": "1.0.0", "description": "__MSG_extDescription__", "default_locale": "zh_CN", - "permissions": ["storage", "activeTab", "scripting", "tabs", "sidePanel"], + "permissions": ["storage", "activeTab", "scripting", "tabs", "sidePanel", "bookmarks"], "host_permissions": [""], "action": { "default_icon": { diff --git a/popup.css b/popup.css index 5244904..a71819d 100644 --- a/popup.css +++ b/popup.css @@ -1154,3 +1154,85 @@ h2 { .strength-info #crackTime { color: #888; } +/* 书签导入对话框 */ +.bookmark-modal-content { + max-width: 420px; +} + +.bookmark-toolbar { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 8px; +} + +.bookmark-select-all { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: #e0e0e0; + cursor: pointer; + white-space: nowrap; +} + +.bookmark-select-all input[type="checkbox"] { + width: auto; +} + +.bookmark-toolbar input[type="text"] { + flex: 1; + padding: 4px 8px; + font-size: 12px; + background: #0f1b35; + border: 1px solid #0f3460; + border-radius: 4px; + color: #e0e0e0; +} + +.bookmark-list { + max-height: 320px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 2px; + margin-bottom: 8px; +} + +.bookmark-item { + display: flex; + align-items: center; + gap: 6px; + padding: 5px 8px; + border-radius: 4px; + cursor: pointer; +} + +.bookmark-item:hover { + background: rgba(15, 52, 96, 0.5); +} + +.bookmark-item input[type="checkbox"] { + width: auto; + flex-shrink: 0; +} + +.bookmark-item-text { + font-size: 12px; + color: #e0e0e0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; + min-width: 0; +} + +.bookmark-item-url { + font-size: 10px; + color: #666; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 1; + min-width: 0; +} diff --git a/popup.html b/popup.html index c4cacdb..002da6d 100644 --- a/popup.html +++ b/popup.html @@ -124,7 +124,7 @@
-

已配置域名

+
+
@@ -310,6 +313,25 @@ + + + diff --git a/popup.js b/popup.js index bd466e1..e64b2d2 100644 --- a/popup.js +++ b/popup.js @@ -66,6 +66,13 @@ document.addEventListener("DOMContentLoaded", () => { const exportBtn = $("exportBtn"); const importBtn = $("importBtn"); const importFile = $("importFile"); + const importBookmarkBtn = $("importBookmarkBtn"); + const bookmarkModal = $("bookmarkModal"); + const bookmarkList = $("bookmarkList"); + const bookmarkSelectAll = $("bookmarkSelectAll"); + const bookmarkSearch = $("bookmarkSearch"); + const bookmarkCancel = $("bookmarkCancel"); + const bookmarkConfirm = $("bookmarkConfirm"); const toast = $("toast"); // 编辑配置弹窗 @@ -1309,6 +1316,139 @@ document.addEventListener("DOMContentLoaded", () => { } }); + // 从书签导入 + function flattenBookmarks(nodes, out) { + for (const node of nodes) { + if (node.url) { + out.push({ title: node.title || node.url, url: node.url }); + } + if (node.children) { + flattenBookmarks(node.children, out); + } + } + } + + function urlToDomain(url) { + try { + const u = new URL(url); + let domain = u.host + u.pathname; + if (domain.endsWith("/") && u.pathname !== "/") domain = domain.replace(/\/$/, ""); + if (u.pathname === "/") domain = u.host; + return domain; + } catch (e) { + return null; + } + } + + let bookmarkItems = []; + + function renderBookmarkList(filter) { + const kw = (filter || "").trim().toLowerCase(); + bookmarkList.innerHTML = ""; + const items = kw + ? bookmarkItems.filter((b) => b.title.toLowerCase().includes(kw) || b.url.toLowerCase().includes(kw)) + : bookmarkItems; + const frag = document.createDocumentFragment(); + for (const b of items) { + const row = document.createElement("label"); + row.className = "bookmark-item"; + const cb = document.createElement("input"); + cb.type = "checkbox"; + cb.dataset.url = b.url; + const text = document.createElement("span"); + text.className = "bookmark-item-text"; + text.textContent = b.title; + const sub = document.createElement("span"); + sub.className = "bookmark-item-url"; + sub.textContent = b.url; + row.appendChild(cb); + row.appendChild(text); + row.appendChild(sub); + frag.appendChild(row); + } + bookmarkList.appendChild(frag); + updateBookmarkSelectAllState(); + } + + function updateBookmarkSelectAllState() { + const cbs = bookmarkList.querySelectorAll("input[type=checkbox]"); + const checked = bookmarkList.querySelectorAll("input[type=checkbox]:checked"); + bookmarkSelectAll.checked = cbs.length > 0 && cbs.length === checked.length; + bookmarkConfirm.disabled = checked.length === 0; + } + + importBookmarkBtn.addEventListener("click", async () => { + try { + const tree = await chrome.bookmarks.getTree(); + bookmarkItems = []; + flattenBookmarks(tree, bookmarkItems); + bookmarkSearch.value = ""; + bookmarkSelectAll.checked = false; + renderBookmarkList(""); + bookmarkModal.classList.add("show"); + } catch (e) { + showToast(t("bookmarkLoadFail")); + } + }); + + bookmarkSearch.addEventListener("input", () => { + renderBookmarkList(bookmarkSearch.value); + }); + + bookmarkList.addEventListener("change", () => { + updateBookmarkSelectAllState(); + }); + + bookmarkSelectAll.addEventListener("change", () => { + const cbs = bookmarkList.querySelectorAll("input[type=checkbox]"); + cbs.forEach((cb) => (cb.checked = bookmarkSelectAll.checked)); + bookmarkConfirm.disabled = !bookmarkSelectAll.checked; + }); + + bookmarkCancel.addEventListener("click", () => { + bookmarkModal.classList.remove("show"); + }); + + bookmarkModal.addEventListener("click", (e) => { + if (e.target === bookmarkModal) bookmarkModal.classList.remove("show"); + }); + + bookmarkConfirm.addEventListener("click", async () => { + const checked = bookmarkList.querySelectorAll("input[type=checkbox]:checked"); + if (!checked.length) return; + const configs = await loadConfigs(); + const existingDomains = new Set(configs.map((c) => c.domain)); + let added = 0; + let skipped = 0; + for (const cb of checked) { + const domain = urlToDomain(cb.dataset.url); + if (!domain || existingDomains.has(domain)) { + skipped++; + continue; + } + existingDomains.add(domain); + configs.push({ + id: Date.now().toString(36) + Math.random().toString(36).slice(2, 8), + domain, + alias: cb.parentElement.querySelector(".bookmark-item-text").textContent || null, + username: "", + password: "", + autoSubmit: true, + usernameSelector: null, + passwordSelector: null, + submitSelector: null, + enabled: true, + delayMs: null, + }); + added++; + } + await saveConfigs(configs); + bookmarkModal.classList.remove("show"); + renderConfigList(); + renderEnvSelect(); + showToast(t("toastBookmarkImported", String(added), String(skipped))); + }); + // 消息发送:SW 刚休眠唤醒时首次 sendMessage 会失败,需重试 function sendMessage(message, retries = 3) { return new Promise((resolve, reject) => {