diff --git a/content.js b/content.js
index 8a6088f..e29cffc 100644
--- a/content.js
+++ b/content.js
@@ -1,13 +1,790 @@
const PERSON_ID_PREFIX = 'chat-topic-person-';
const ICON_ID_PREFIX = "presence-pill-";
const CHAT_ROSTER_PREFIX = "chat-roster-item-name-";
+const CHAT_TITLE_PREFIX = "title-chat-list-item_";
const SUGGEST_PEOPLE_PREFIX = "AUTOSUGGEST_SUGGESTION_PEOPLE";
const ROSTER_AVATAR_PREFIX = "roster-avatar-img-";
const SERP_PEOPLE_CARD_PREFIX = "serp-people-card-content-";
const PEOPLE_PICKER_PREFIX = "people-picker-entry-";
const PEOPLE_PICKER_SEL_PREFIX = "people-picker-selected-user-";
+const CHAT_CATEGORY_STORAGE_KEY = "chatCategories";
+const CHAT_CACHE_STORAGE_KEY = "chatCategoryChatCache";
+const CHAT_ORGANIZER_ROOT_ID = "teams-alias-chat-organizer-root";
+const CHAT_ORGANIZER_STYLE_ID = "teams-alias-chat-organizer-style";
+const CHAT_ORGANIZER_BUTTON_ID = "teams-alias-chat-organizer-button";
let debounceTimer = null;
let isMutating = false;
+let organizerStateLoaded = false;
+let organizerSaveTimer = null;
+const chatOrganizerState = {
+ categories: [],
+ chatCache: {},
+ visibleChats: {},
+ isModalOpen: false,
+ selectedCategoryId: "",
+ feedback: "",
+ draftCategoryName: "",
+ draftChatId: "",
+ visibleChatsExpanded: false,
+ expandedCategoryIds: {}
+};
+
+function getOrganizerRoot() {
+ return document.getElementById(CHAT_ORGANIZER_ROOT_ID);
+}
+
+function escapeHtml(value) {
+ return String(value ?? "")
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
+function normalizeChatId(rawValue) {
+ if (!rawValue) return "";
+ let value = String(rawValue).trim();
+ if (value.startsWith(CHAT_TITLE_PREFIX)) {
+ value = value.slice(CHAT_TITLE_PREFIX.length);
+ }
+ return value.startsWith("19:") ? value : "";
+}
+
+function createCategoryId() {
+ return `cat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+}
+
+function setOrganizerFeedback(message) {
+ chatOrganizerState.feedback = message || "";
+ renderChatOrganizer();
+}
+
+function scheduleOrganizerStateSave() {
+ clearTimeout(organizerSaveTimer);
+ organizerSaveTimer = setTimeout(async () => {
+ await chrome.storage.local.set({
+ [CHAT_CATEGORY_STORAGE_KEY]: chatOrganizerState.categories,
+ [CHAT_CACHE_STORAGE_KEY]: chatOrganizerState.chatCache
+ });
+ }, 300);
+}
+
+async function loadChatOrganizerState() {
+ if (organizerStateLoaded) return;
+ const result = await chrome.storage.local.get([
+ CHAT_CATEGORY_STORAGE_KEY,
+ CHAT_CACHE_STORAGE_KEY
+ ]);
+
+ chatOrganizerState.categories = Array.isArray(result[CHAT_CATEGORY_STORAGE_KEY])
+ ? result[CHAT_CATEGORY_STORAGE_KEY]
+ : [];
+ chatOrganizerState.chatCache = result[CHAT_CACHE_STORAGE_KEY] || {};
+ chatOrganizerState.selectedCategoryId = chatOrganizerState.categories[0]?.id || "";
+ organizerStateLoaded = true;
+}
+
+function getCategoryById(categoryId) {
+ return chatOrganizerState.categories.find(category => category.id === categoryId) || null;
+}
+
+function getChatMeta(chatId) {
+ return chatOrganizerState.visibleChats[chatId] || chatOrganizerState.chatCache[chatId] || null;
+}
+
+function inferAvatarFallback(name) {
+ const base = (name || "?").trim();
+ return base.slice(0, 2).toUpperCase();
+}
+
+function findChatTitleElement(chatId) {
+ const normalizedChatId = normalizeChatId(chatId);
+ if (!normalizedChatId) return null;
+ return document.getElementById(`${CHAT_TITLE_PREFIX}${normalizedChatId}`);
+}
+
+function findClickableChatElement(chatId) {
+ const titleElement = findChatTitleElement(chatId);
+ if (!titleElement) return null;
+
+ const clickable = titleElement.closest('button, a, [role="button"], [role="link"], [role="listitem"]');
+ if (clickable) return clickable;
+
+ let current = titleElement;
+ for (let depth = 0; depth < 8 && current; depth += 1) {
+ if (typeof current.click === "function") {
+ return current;
+ }
+ current = current.parentElement;
+ }
+ return titleElement;
+}
+
+function deepLinkToChat(chatId) {
+ const normalizedChatId = normalizeChatId(chatId);
+ if (!normalizedChatId) return false;
+
+ const candidates = [
+ `${window.location.origin}/l/chat/${encodeURIComponent(normalizedChatId)}/conversations`,
+ `https://teams.microsoft.com/l/chat/${encodeURIComponent(normalizedChatId)}/conversations`
+ ];
+
+ for (const url of candidates) {
+ try {
+ window.location.href = url;
+ return true;
+ } catch (error) {}
+ }
+ return false;
+}
+
+function openChatById(chatId) {
+ const clickableElement = findClickableChatElement(chatId);
+ if (clickableElement) {
+ clickableElement.click();
+ return true;
+ }
+ return deepLinkToChat(chatId);
+}
+
+function extractChatAvatar(titleElement) {
+ const rowRoot = titleElement.closest('[role="listitem"], button, a, [role="button"]') || titleElement.parentElement;
+ if (!rowRoot) return "";
+
+ const avatarImage = rowRoot.querySelector("img");
+ if (avatarImage?.src) {
+ return avatarImage.src;
+ }
+
+ const avatarCandidate = rowRoot.querySelector('[style*="background-image"]');
+ const inlineStyle = avatarCandidate?.style?.backgroundImage || "";
+ const matched = inlineStyle.match(/url\(["']?(.*?)["']?\)/);
+ return matched?.[1] || "";
+}
+
+function scanVisibleChats() {
+ const titleElements = document.querySelectorAll(`[id^="${CHAT_TITLE_PREFIX}"]`);
+ let changed = false;
+ const nextVisibleChats = {};
+
+ titleElements.forEach(titleElement => {
+ const chatId = normalizeChatId(titleElement.id);
+ if (!chatId) return;
+
+ const name = titleElement.textContent?.trim() || chatOrganizerState.chatCache[chatId]?.name || chatId;
+ const avatarUrl = extractChatAvatar(titleElement) || chatOrganizerState.chatCache[chatId]?.avatarUrl || "";
+ const chatMeta = {
+ id: chatId,
+ name,
+ avatarUrl,
+ updatedAt: Date.now()
+ };
+
+ nextVisibleChats[chatId] = chatMeta;
+
+ const cached = chatOrganizerState.chatCache[chatId];
+ if (!cached || cached.name !== chatMeta.name || cached.avatarUrl !== chatMeta.avatarUrl) {
+ chatOrganizerState.chatCache[chatId] = chatMeta;
+ changed = true;
+ }
+ });
+
+ const visibleIds = Object.keys(nextVisibleChats);
+ const previousVisibleIds = Object.keys(chatOrganizerState.visibleChats);
+ if (visibleIds.length !== previousVisibleIds.length || visibleIds.some(id => !chatOrganizerState.visibleChats[id])) {
+ changed = true;
+ }
+
+ chatOrganizerState.visibleChats = nextVisibleChats;
+
+ if (changed) {
+ scheduleOrganizerStateSave();
+ renderChatOrganizer();
+ }
+}
+
+function addChatToCategory(categoryId, rawChatId) {
+ const category = getCategoryById(categoryId);
+ const chatId = normalizeChatId(rawChatId);
+
+ if (!category) {
+ setOrganizerFeedback("请选择一个分类。");
+ return;
+ }
+ if (!chatId) {
+ setOrganizerFeedback("聊天 ID 格式无效,必须以 19: 开头。");
+ return;
+ }
+ if (category.chatIds.includes(chatId)) {
+ setOrganizerFeedback("该聊天已存在于当前分类。");
+ return;
+ }
+
+ category.chatIds.push(chatId);
+ const meta = getChatMeta(chatId);
+ if (meta) {
+ chatOrganizerState.chatCache[chatId] = {
+ ...chatOrganizerState.chatCache[chatId],
+ ...meta,
+ updatedAt: Date.now()
+ };
+ } else {
+ chatOrganizerState.chatCache[chatId] = {
+ id: chatId,
+ name: chatId,
+ avatarUrl: "",
+ updatedAt: Date.now()
+ };
+ }
+
+ scheduleOrganizerStateSave();
+ setOrganizerFeedback("已加入分类。");
+}
+
+function removeChatFromCategory(categoryId, chatId) {
+ const category = getCategoryById(categoryId);
+ if (!category) return;
+ category.chatIds = category.chatIds.filter(id => id !== chatId);
+ scheduleOrganizerStateSave();
+ setOrganizerFeedback("已移除聊天。");
+}
+
+function createCategory(name) {
+ const trimmedName = (name || "").trim();
+ if (!trimmedName) {
+ setOrganizerFeedback("分类名称不能为空。");
+ return;
+ }
+
+ const category = {
+ id: createCategoryId(),
+ name: trimmedName,
+ chatIds: []
+ };
+
+ chatOrganizerState.categories.push(category);
+ chatOrganizerState.expandedCategoryIds[category.id] = true;
+ chatOrganizerState.selectedCategoryId = category.id;
+ scheduleOrganizerStateSave();
+ setOrganizerFeedback("分类已创建。");
+}
+
+function deleteCategory(categoryId) {
+ chatOrganizerState.categories = chatOrganizerState.categories.filter(category => category.id !== categoryId);
+ delete chatOrganizerState.expandedCategoryIds[categoryId];
+ if (chatOrganizerState.selectedCategoryId === categoryId) {
+ chatOrganizerState.selectedCategoryId = chatOrganizerState.categories[0]?.id || "";
+ }
+ scheduleOrganizerStateSave();
+ setOrganizerFeedback("分类已删除。");
+}
+
+function isCategoryExpanded(categoryId) {
+ return Boolean(chatOrganizerState.expandedCategoryIds[categoryId]);
+}
+
+function toggleCategoryExpanded(categoryId) {
+ chatOrganizerState.expandedCategoryIds[categoryId] = !isCategoryExpanded(categoryId);
+ renderChatOrganizer();
+}
+
+function toggleVisibleChatsExpanded() {
+ chatOrganizerState.visibleChatsExpanded = !chatOrganizerState.visibleChatsExpanded;
+ renderChatOrganizer();
+}
+
+function renderChatCard(chatId, categoryId) {
+ const meta = getChatMeta(chatId) || { id: chatId, name: chatId, avatarUrl: "" };
+ const avatar = meta.avatarUrl
+ ? ``
+ : `${escapeHtml(inferAvatarFallback(meta.name))}`;
+
+ return `
+