add classification

This commit is contained in:
Julian Freeman
2026-04-10 21:36:08 -04:00
parent b849b08386
commit ce67ae56bd

View File

@@ -1,13 +1,790 @@
const PERSON_ID_PREFIX = 'chat-topic-person-'; const PERSON_ID_PREFIX = 'chat-topic-person-';
const ICON_ID_PREFIX = "presence-pill-"; const ICON_ID_PREFIX = "presence-pill-";
const CHAT_ROSTER_PREFIX = "chat-roster-item-name-"; const CHAT_ROSTER_PREFIX = "chat-roster-item-name-";
const CHAT_TITLE_PREFIX = "title-chat-list-item_";
const SUGGEST_PEOPLE_PREFIX = "AUTOSUGGEST_SUGGESTION_PEOPLE"; const SUGGEST_PEOPLE_PREFIX = "AUTOSUGGEST_SUGGESTION_PEOPLE";
const ROSTER_AVATAR_PREFIX = "roster-avatar-img-"; const ROSTER_AVATAR_PREFIX = "roster-avatar-img-";
const SERP_PEOPLE_CARD_PREFIX = "serp-people-card-content-"; const SERP_PEOPLE_CARD_PREFIX = "serp-people-card-content-";
const PEOPLE_PICKER_PREFIX = "people-picker-entry-"; const PEOPLE_PICKER_PREFIX = "people-picker-entry-";
const PEOPLE_PICKER_SEL_PREFIX = "people-picker-selected-user-"; 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 debounceTimer = null;
let isMutating = false; 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, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
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
? `<img src="${escapeHtml(meta.avatarUrl)}" alt="" class="teams-alias-chat-avatar-image">`
: `<span class="teams-alias-chat-avatar-fallback">${escapeHtml(inferAvatarFallback(meta.name))}</span>`;
return `
<div class="teams-alias-chat-card">
<button class="teams-alias-chat-open" data-action="open-chat" data-chat-id="${escapeHtml(chatId)}">
<span class="teams-alias-chat-avatar">${avatar}</span>
<span class="teams-alias-chat-main">
<span class="teams-alias-chat-name">${escapeHtml(meta.name || chatId)}</span>
<span class="teams-alias-chat-id">${escapeHtml(chatId)}</span>
</span>
</button>
<button class="teams-alias-chat-remove" data-action="remove-chat" data-category-id="${escapeHtml(categoryId)}" data-chat-id="${escapeHtml(chatId)}">移除</button>
</div>
`;
}
function renderVisibleChatCandidate(chatId) {
const meta = chatOrganizerState.visibleChats[chatId];
if (!meta) return "";
const avatar = meta.avatarUrl
? `<img src="${escapeHtml(meta.avatarUrl)}" alt="" class="teams-alias-chat-avatar-image">`
: `<span class="teams-alias-chat-avatar-fallback">${escapeHtml(inferAvatarFallback(meta.name))}</span>`;
return `
<div class="teams-alias-visible-chat">
<span class="teams-alias-chat-avatar">${avatar}</span>
<span class="teams-alias-chat-main">
<span class="teams-alias-chat-name">${escapeHtml(meta.name || chatId)}</span>
<span class="teams-alias-chat-id">${escapeHtml(chatId)}</span>
</span>
<button data-action="quick-add" data-chat-id="${escapeHtml(chatId)}">加入分类</button>
</div>
`;
}
function ensureChatOrganizerUI() {
if (!document.body || getOrganizerRoot()) return;
if (!document.getElementById(CHAT_ORGANIZER_STYLE_ID)) {
const style = document.createElement("style");
style.id = CHAT_ORGANIZER_STYLE_ID;
style.textContent = `
#${CHAT_ORGANIZER_BUTTON_ID} {
position: fixed;
top: 12px;
left: 12px;
z-index: 2147483644;
border: none;
border-radius: 999px;
background: #2563eb;
color: #fff;
font-size: 13px;
font-weight: 600;
padding: 10px 14px;
cursor: pointer;
box-shadow: 0 10px 30px rgba(37, 99, 235, 0.25);
}
#${CHAT_ORGANIZER_ROOT_ID} {
position: fixed;
inset: 0;
z-index: 2147483643;
display: none;
}
#${CHAT_ORGANIZER_ROOT_ID}.is-open {
display: block;
}
.teams-alias-overlay {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, 0.4);
backdrop-filter: blur(3px);
}
.teams-alias-modal {
position: absolute;
top: 64px;
left: 24px;
width: min(920px, calc(100vw - 48px));
max-height: calc(100vh - 88px);
overflow: auto;
border-radius: 18px;
background: #ffffff;
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.22);
color: #0f172a;
font-family: "Segoe UI", sans-serif;
}
.teams-alias-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18px 20px 12px;
border-bottom: 1px solid #e5e7eb;
}
.teams-alias-modal-header h2 {
margin: 0;
font-size: 18px;
}
.teams-alias-close {
border: none;
background: #e2e8f0;
color: #0f172a;
border-radius: 999px;
width: 32px;
height: 32px;
cursor: pointer;
}
.teams-alias-modal-body {
padding: 16px 20px 20px;
display: grid;
gap: 16px;
}
.teams-alias-panel {
border: 1px solid #e5e7eb;
border-radius: 14px;
padding: 14px;
background: #f8fafc;
}
.teams-alias-panel h3 {
margin: 0 0 12px;
font-size: 14px;
}
.teams-alias-row {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
}
.teams-alias-row input,
.teams-alias-row select {
min-width: 180px;
flex: 1;
border: 1px solid #cbd5e1;
border-radius: 10px;
padding: 10px 12px;
font-size: 13px;
background: #fff;
}
.teams-alias-row button,
.teams-alias-visible-chat button,
.teams-alias-chat-remove {
border: none;
border-radius: 10px;
padding: 9px 12px;
font-size: 13px;
cursor: pointer;
background: #2563eb;
color: #fff;
}
.teams-alias-category-list,
.teams-alias-visible-list,
.teams-alias-chat-list {
display: grid;
gap: 10px;
}
.teams-alias-category-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
border: 1px solid #dbeafe;
border-radius: 12px;
padding: 12px;
background: #fff;
}
.teams-alias-section-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.teams-alias-section-header h3 {
margin: 0;
}
.teams-alias-ghost-button,
.teams-alias-category-actions button {
border: 1px solid #cbd5e1;
border-radius: 10px;
padding: 8px 12px;
font-size: 12px;
background: #fff;
color: #0f172a;
cursor: pointer;
}
.teams-alias-category-actions {
display: flex;
gap: 8px;
align-items: center;
}
.teams-alias-category-meta {
display: grid;
gap: 4px;
}
.teams-alias-category-name {
font-size: 14px;
font-weight: 600;
}
.teams-alias-category-count,
.teams-alias-chat-id,
.teams-alias-feedback {
font-size: 12px;
color: #475569;
}
.teams-alias-chat-card,
.teams-alias-visible-chat {
display: flex;
align-items: center;
gap: 10px;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 10px;
background: #fff;
}
.teams-alias-chat-open {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
border: none;
padding: 0;
background: transparent;
cursor: pointer;
color: inherit;
text-align: left;
}
.teams-alias-chat-avatar {
width: 36px;
height: 36px;
border-radius: 999px;
overflow: hidden;
display: inline-flex;
align-items: center;
justify-content: center;
background: #dbeafe;
color: #1d4ed8;
font-size: 12px;
font-weight: 700;
flex-shrink: 0;
}
.teams-alias-chat-avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.teams-alias-chat-main {
min-width: 0;
display: grid;
gap: 3px;
}
.teams-alias-chat-name {
font-size: 13px;
font-weight: 600;
color: #0f172a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.teams-alias-empty {
color: #64748b;
font-size: 13px;
}
.teams-alias-collapsed {
display: none;
}
@media (max-width: 900px) {
.teams-alias-modal {
left: 12px;
right: 12px;
width: auto;
top: 56px;
}
}
`;
document.head.appendChild(style);
}
const organizerButton = document.createElement("button");
organizerButton.id = CHAT_ORGANIZER_BUTTON_ID;
organizerButton.textContent = "聊天分类";
organizerButton.addEventListener("click", () => {
chatOrganizerState.isModalOpen = true;
renderChatOrganizer();
});
const root = document.createElement("div");
root.id = CHAT_ORGANIZER_ROOT_ID;
root.addEventListener("click", event => {
const actionElement = event.target.closest("[data-action]");
if (event.target.classList.contains("teams-alias-overlay")) {
chatOrganizerState.isModalOpen = false;
renderChatOrganizer();
return;
}
if (!actionElement) return;
const action = actionElement.getAttribute("data-action");
if (action === "close-modal") {
chatOrganizerState.isModalOpen = false;
renderChatOrganizer();
return;
}
if (action === "create-category") {
const input = root.querySelector('[data-role="new-category-name"]');
createCategory(input?.value || "");
chatOrganizerState.draftCategoryName = "";
return;
}
if (action === "delete-category") {
deleteCategory(actionElement.getAttribute("data-category-id"));
return;
}
if (action === "toggle-category") {
toggleCategoryExpanded(actionElement.getAttribute("data-category-id"));
return;
}
if (action === "toggle-visible-chats") {
toggleVisibleChatsExpanded();
return;
}
if (action === "add-chat-by-id") {
const input = root.querySelector('[data-role="chat-id-input"]');
addChatToCategory(chatOrganizerState.selectedCategoryId, input?.value || "");
chatOrganizerState.draftChatId = "";
return;
}
if (action === "quick-add") {
addChatToCategory(chatOrganizerState.selectedCategoryId, actionElement.getAttribute("data-chat-id"));
return;
}
if (action === "remove-chat") {
removeChatFromCategory(
actionElement.getAttribute("data-category-id"),
actionElement.getAttribute("data-chat-id")
);
return;
}
if (action === "open-chat") {
const chatId = actionElement.getAttribute("data-chat-id");
const opened = openChatById(chatId);
setOrganizerFeedback(opened ? "已尝试打开聊天。" : "未找到该聊天,且 deep link 跳转失败。");
}
});
root.addEventListener("change", event => {
const target = event.target;
if (target.matches('[data-role="category-select"]')) {
chatOrganizerState.selectedCategoryId = target.value;
renderChatOrganizer();
}
});
root.addEventListener("input", event => {
const target = event.target;
if (target.matches('[data-role="new-category-name"]')) {
chatOrganizerState.draftCategoryName = target.value;
return;
}
if (target.matches('[data-role="chat-id-input"]')) {
chatOrganizerState.draftChatId = target.value;
}
});
document.body.appendChild(organizerButton);
document.body.appendChild(root);
renderChatOrganizer();
}
function renderChatOrganizer() {
const root = getOrganizerRoot();
if (!root) return;
root.classList.toggle("is-open", chatOrganizerState.isModalOpen);
if (!chatOrganizerState.isModalOpen) {
root.innerHTML = "";
return;
}
const categoryOptions = chatOrganizerState.categories.map(category => `
<option value="${escapeHtml(category.id)}" ${category.id === chatOrganizerState.selectedCategoryId ? "selected" : ""}>
${escapeHtml(category.name)}
</option>
`).join("");
const categoriesMarkup = chatOrganizerState.categories.length
? chatOrganizerState.categories.map(category => `
<div class="teams-alias-category-item">
<div class="teams-alias-category-meta">
<span class="teams-alias-category-name">${escapeHtml(category.name)}</span>
<span class="teams-alias-category-count">${category.chatIds.length} 个聊天</span>
</div>
<div class="teams-alias-category-actions">
<button data-action="toggle-category" data-category-id="${escapeHtml(category.id)}">${isCategoryExpanded(category.id) ? "收起" : "展开"}</button>
<button data-action="delete-category" data-category-id="${escapeHtml(category.id)}">删除分类</button>
</div>
</div>
<div class="teams-alias-chat-list ${isCategoryExpanded(category.id) ? "" : "teams-alias-collapsed"}">
${category.chatIds.length ? category.chatIds.map(chatId => renderChatCard(chatId, category.id)).join("") : '<div class="teams-alias-empty">当前分类还没有聊天。</div>'}
</div>
`).join("")
: '<div class="teams-alias-empty">还没有分类,先创建一个。</div>';
const visibleChatIds = Object.keys(chatOrganizerState.visibleChats);
const visibleChatsMarkup = visibleChatIds.length
? visibleChatIds.map(chatId => renderVisibleChatCandidate(chatId)).join("")
: '<div class="teams-alias-empty">当前页面还没有识别到左侧聊天列表。</div>';
root.innerHTML = `
<div class="teams-alias-overlay"></div>
<div class="teams-alias-modal" role="dialog" aria-modal="true" aria-label="聊天分类管理">
<div class="teams-alias-modal-header">
<h2>聊天分类管理</h2>
<button class="teams-alias-close" data-action="close-modal">×</button>
</div>
<div class="teams-alias-modal-body">
<div class="teams-alias-panel">
<h3>创建分类</h3>
<div class="teams-alias-row">
<input type="text" placeholder="例如:重点客户 / 项目群" data-role="new-category-name" value="${escapeHtml(chatOrganizerState.draftCategoryName)}">
<button data-action="create-category">创建</button>
</div>
</div>
<div class="teams-alias-panel">
<h3>加入聊天</h3>
<div class="teams-alias-row">
<select data-role="category-select">
<option value="">请选择分类</option>
${categoryOptions}
</select>
<input type="text" placeholder="输入 19: 开头的聊天 ID" data-role="chat-id-input" value="${escapeHtml(chatOrganizerState.draftChatId)}">
<button data-action="add-chat-by-id">按 ID 添加</button>
</div>
<div class="teams-alias-feedback">${escapeHtml(chatOrganizerState.feedback || "点击聊天卡片会在原 Teams 界面中打开会话。")}</div>
</div>
<div class="teams-alias-panel">
<div class="teams-alias-section-header">
<h3>当前识别到的聊天</h3>
<button class="teams-alias-ghost-button" data-action="toggle-visible-chats">${chatOrganizerState.visibleChatsExpanded ? "收起" : "展开"}</button>
</div>
<div class="teams-alias-visible-list ${chatOrganizerState.visibleChatsExpanded ? "" : "teams-alias-collapsed"}">${visibleChatsMarkup}</div>
</div>
<div class="teams-alias-panel">
<h3>分类内容</h3>
<div class="teams-alias-category-list">${categoriesMarkup}</div>
</div>
</div>
</div>
`;
}
async function getAlias(id) { async function getAlias(id) {
const key = id.replace(PERSON_ID_PREFIX, ""); const key = id.replace(PERSON_ID_PREFIX, "");
@@ -315,7 +1092,6 @@ function applyPeopleInCall(el) {
}); });
} }
} }
} }
// 回应表情的人员别名 // 回应表情的人员别名
@@ -373,10 +1149,18 @@ function applyToAll() {
const allReaction = document.querySelectorAll(`[data-tid="diverse-reaction-user-list-item"]`); const allReaction = document.querySelectorAll(`[data-tid="diverse-reaction-user-list-item"]`);
allReaction.forEach(el => applyReactionAlias(el)); allReaction.forEach(el => applyReactionAlias(el));
ensureChatOrganizerUI();
scanVisibleChats();
} }
// 初始化逻辑 // 初始化逻辑
function init() { function init() {
loadChatOrganizerState().then(() => {
ensureChatOrganizerUI();
scanVisibleChats();
});
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {
clearTimeout(debounceTimer); clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => { debounceTimer = setTimeout(() => {