diff --git a/content.js b/content.js index 2dae07e..b1fcd96 100644 --- a/content.js +++ b/content.js @@ -312,6 +312,96 @@ function toggleAddPanel() { renderChatOrganizer(); } +function normalizeImportedCategories(rawCategories) { + if (!Array.isArray(rawCategories)) return []; + + return rawCategories + .map(category => { + const name = String(category?.name || "").trim(); + if (!name) return null; + + const chatIds = Array.isArray(category?.chatIds) + ? Array.from(new Set(category.chatIds.map(chatId => normalizeChatId(chatId)).filter(Boolean))) + : []; + + return { + id: String(category?.id || createCategoryId()), + name, + chatIds + }; + }) + .filter(Boolean); +} + +function normalizeImportedChatCache(rawChatCache) { + if (!rawChatCache || typeof rawChatCache !== "object") return {}; + + const nextCache = {}; + Object.entries(rawChatCache).forEach(([rawChatId, meta]) => { + const chatId = normalizeChatId(rawChatId); + if (!chatId) return; + + nextCache[chatId] = { + id: chatId, + name: String(meta?.name || chatId), + avatarUrl: String(meta?.avatarUrl || ""), + updatedAt: Number(meta?.updatedAt || Date.now()) + }; + }); + return nextCache; +} + +function exportChatCategories() { + const payload = { + version: 1, + exportedAt: new Date().toISOString(), + categories: chatOrganizerState.categories, + chatCache: chatOrganizerState.chatCache + }; + + const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = `teams-chat-categories-${timestamp}.json`; + anchor.click(); + URL.revokeObjectURL(url); + setOrganizerFeedback("分类已导出。"); +} + +async function importChatCategoriesFromFile(file) { + if (!file) return; + + try { + const content = await file.text(); + const parsed = JSON.parse(content); + const importedCategories = normalizeImportedCategories(parsed?.categories); + const importedChatCache = normalizeImportedChatCache(parsed?.chatCache); + + if (!importedCategories.length) { + setOrganizerFeedback("导入失败:文件里没有有效分类。"); + return; + } + + chatOrganizerState.categories = importedCategories; + chatOrganizerState.chatCache = { + ...chatOrganizerState.chatCache, + ...importedChatCache + }; + chatOrganizerState.expandedCategoryIds = {}; + importedCategories.forEach(category => { + chatOrganizerState.expandedCategoryIds[category.id] = true; + }); + chatOrganizerState.selectedCategoryId = importedCategories[0]?.id || ""; + scheduleOrganizerStateSave(); + setOrganizerFeedback(`已导入 ${importedCategories.length} 个分类。`); + } catch (error) { + console.error("Teams Alias: 导入分类失败", error); + setOrganizerFeedback("导入失败:文件格式不是有效的 JSON。"); + } +} + function renderChatCard(chatId, categoryId) { const meta = getChatMeta(chatId) || { id: chatId, name: chatId, avatarUrl: "" }; const avatar = meta.avatarUrl @@ -502,6 +592,17 @@ function ensureChatOrganizerUI() { font-weight: 600; } + .teams-alias-secondary-button { + border: 1px solid #cbd5e1; + border-radius: 10px; + padding: 9px 14px; + font-size: 13px; + cursor: pointer; + background: #fff; + color: #0f172a; + font-weight: 600; + } + .teams-alias-category-list, .teams-alias-visible-list, .teams-alias-chat-list { @@ -635,13 +736,6 @@ function ensureChatOrganizerUI() { display: none; } - .teams-alias-main-title { - margin: 0; - font-size: 14px; - font-weight: 600; - color: #334155; - } - @media (max-width: 900px) { .teams-alias-modal { left: 12px; @@ -649,6 +743,11 @@ function ensureChatOrganizerUI() { width: auto; top: 56px; } + + .teams-alias-toolbar { + align-items: flex-start; + flex-direction: column; + } } `; document.head.appendChild(style); @@ -704,6 +803,16 @@ function ensureChatOrganizerUI() { return; } + if (action === "export-categories") { + exportChatCategories(); + return; + } + + if (action === "trigger-import-categories") { + root.querySelector('[data-role="import-categories-input"]')?.click(); + return; + } + if (action === "toggle-category") { toggleCategoryExpanded(actionElement.getAttribute("data-category-id")); return; @@ -747,6 +856,12 @@ function ensureChatOrganizerUI() { if (target.matches('[data-role="category-select"]')) { chatOrganizerState.selectedCategoryId = target.value; renderChatOrganizer(); + return; + } + if (target.matches('[data-role="import-categories-input"]')) { + const file = target.files?.[0]; + importChatCategoriesFromFile(file); + target.value = ""; } }); @@ -813,11 +928,13 @@ function renderChatOrganizer() {