add classification
This commit is contained in:
786
content.js
786
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, """)
|
||||
.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
|
||||
? `<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) {
|
||||
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"]`);
|
||||
allReaction.forEach(el => applyReactionAlias(el));
|
||||
|
||||
ensureChatOrganizerUI();
|
||||
scanVisibleChats();
|
||||
}
|
||||
|
||||
// 初始化逻辑
|
||||
function init() {
|
||||
loadChatOrganizerState().then(() => {
|
||||
ensureChatOrganizerUI();
|
||||
scanVisibleChats();
|
||||
});
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
|
||||
Reference in New Issue
Block a user