// 聊天室在线用户列表渲染:名单、搜索、徽标轮换。
// 从 Blade 内联脚本 scripts.blade.php 迁移至 Vite 模块。
import { escapeHtml } from "./html.js";
import { normalizeDailyStatus } from "./preferences-status.js";
// ── 每日状态解析 ──
function resolveUserDailyStatus(user) {
return normalizeDailyStatus(user);
}
// ── 构建职务 / 管理员徽标 ──
function buildUserPrimaryBadgeHtml(user, username) {
if (user.position_icon) {
const posTitle = (user.position_name || "在职") + " · " + username;
const safePosTitle = escapeHtml(String(posTitle));
const safePositionIcon = escapeHtml(String(user.position_icon || "🎖️"));
return `${safePositionIcon}`;
}
if (user.is_admin) {
return `🎖️`;
}
return "";
}
// ── 构建用户 VIP 徽标 ──
function buildUserVipBadgeHtml(user) {
if (!user.vip_icon) {
return "";
}
const vipColor = user.vip_color || "#f59e0b";
const safeVipTitle = escapeHtml(String(user.vip_name || "VIP"));
const safeVipIcon = escapeHtml(String(user.vip_icon || "👑"));
return `${safeVipIcon}`;
}
// ── 构建状态徽标 ──
function buildUserStatusBadgeHtml(user) {
const status = resolveUserDailyStatus(user);
if (!status) {
return "";
}
const safeIcon = escapeHtml(status.icon);
const safeTooltip = escapeHtml(`${status.group} · ${status.label}`);
return `${safeIcon}`;
}
// ── 构建签到身份徽标 ──
function buildUserSignIdentityBadgeHtml(user) {
const identityKey = String(user.sign_identity_key ?? user.sign_identity ?? "");
const identityIcon = String(user.sign_identity_icon ?? "");
if (!identityKey || !identityIcon) {
return "";
}
const identityLabel = String(user.sign_identity_label ?? user.sign_identity_name ?? "");
const safeIcon = escapeHtml(identityIcon);
const safeTooltip = escapeHtml(identityLabel ? `签到 · ${identityLabel}` : "签到身份");
return `${safeIcon}`;
}
/**
* 按轮换节奏在签到身份、状态、职务/管理、VIP 徽标之间轮换。
*/
export function buildUserBadgeHtml(user, username) {
const state = window.chatState;
const tick = state ? state.userBadgeRotationTick : 0;
const badges = [
buildUserSignIdentityBadgeHtml(user),
buildUserStatusBadgeHtml(user),
buildUserPrimaryBadgeHtml(user, username),
buildUserVipBadgeHtml(user),
].filter(Boolean);
if (badges.length === 0) {
return "";
}
return badges[tick % badges.length];
}
/**
* 仅刷新当前已渲染用户行的徽标槽位,避免重建头像节点造成闪烁。
*/
export function refreshRenderedUserBadges(scope = document) {
const state = window.chatState;
const onlineUsers = state ? state.onlineUsers : (window.onlineUsers || {});
scope.querySelectorAll(".user-item[data-username]").forEach((item) => {
const username = item.dataset.username;
const badgeSlot = item.querySelector(".user-badge-slot");
if (!username || !badgeSlot) {
return;
}
badgeSlot.innerHTML = buildUserBadgeHtml(onlineUsers[username] || {}, username);
});
}
/**
* 核心渲染函数:将在线用户渲染到指定容器(桌面端名单区和手机端抽屉共用)。
*/
export function renderUserListToContainer(targetContainer, sortBy, keyword) {
if (!targetContainer) return;
const state = window.chatState;
const onlineUsers = state ? state.onlineUsers : (window.onlineUsers || {});
const fragment = document.createDocumentFragment();
// 在列表顶部添加"大家"条目
const allDiv = document.createElement("div");
allDiv.className = "user-item";
allDiv.dataset.userListEveryone = "1";
allDiv.innerHTML = '大家';
fragment.appendChild(allDiv);
// 构建用户数组并排序
let userArr = [];
for (let username in onlineUsers) {
userArr.push({ username, ...onlineUsers[username] });
}
if (sortBy === "name") {
userArr.sort((a, b) => a.username.localeCompare(b.username, "zh"));
} else if (sortBy === "level") {
userArr.sort((a, b) => (b.user_level || 0) - (a.user_level || 0));
}
userArr.forEach((user) => {
const username = user.username;
// 搜索过滤
if (keyword && !username.toLowerCase().includes(keyword)) return;
const item = document.createElement("div");
item.className = "user-item";
item.dataset.username = username;
item.dataset.userListEntry = "1";
const headface = (user.headface || "1.gif");
const headImgSrc = headface.startsWith("storage/") ? "/" + headface : "/images/headface/" + headface;
const badges = buildUserBadgeHtml(user, username);
// 女生名字使用玫粉色
const nameColor = (user.sex == 2) ? "color:#e91e8c;" : "";
// 昵称颜色装扮
let userNameExtraClass = "";
if (user.name_color) {
userNameExtraClass = " msg-name--" + user.name_color.replace(/^msg_name_/, "");
}
// 头像框装扮
let avatarHtml = "";
if (user.avatar_frame) {
const frameClass = "avatar-frame--" + user.avatar_frame.replace(/^avatar_frame_/, "");
avatarHtml = '' +
'' +
'
' +
'';
} else {
avatarHtml = '
';
}
item.innerHTML = `
${avatarHtml}
${badges}
`;
// 具体点击、双击与手机双触发由 Vite 的 right-panel.js 统一事件委托处理
fragment.appendChild(item);
});
targetContainer.replaceChildren(fragment);
refreshRenderedUserBadges(targetContainer);
}
/**
* 渲染完整用户列表(含下拉选单与在线计数)。
*/
export function renderUserList() {
const state = window.chatState;
if (!state) return;
if (state.userListRenderTimer) {
window.clearTimeout(state.userListRenderTimer);
state.userListRenderTimer = null;
}
const userList = state.userList;
const toUserSelect = state.toUserSelect;
// 获取排序方式和搜索词
const sortSelect = document.getElementById("user-sort-select");
const sortBy = sortSelect ? sortSelect.value : "default";
const searchInput = document.getElementById("user-search-input");
const keyword = searchInput ? searchInput.value.trim().toLowerCase() : "";
// 调用核心渲染
if (userList) {
renderUserListToContainer(userList, sortBy, keyword);
}
// 下拉框重建
if (toUserSelect) {
const selectFragment = document.createDocumentFragment();
const everyoneOption = document.createElement("option");
everyoneOption.value = "大家";
everyoneOption.textContent = "大家";
selectFragment.appendChild(everyoneOption);
for (let username in state.onlineUsers) {
if (username !== window.chatContext?.username) {
const option = document.createElement("option");
option.value = username;
option.textContent = username === "AI小班长" ? "🤖 AI小班长" : username;
selectFragment.appendChild(option);
}
}
toUserSelect.replaceChildren(selectFragment);
}
// 在线计数
const count = Object.keys(state.onlineUsers).length;
const onlineCount = state.onlineCount;
const onlineCountBottom = state.onlineCountBottom;
if (onlineCount) onlineCount.innerText = count;
if (onlineCountBottom) onlineCountBottom.innerText = count;
const footer = document.getElementById("online-count-footer");
if (footer) footer.innerText = count;
// 派发用户列表更新事件,供手机端抽屉同步
window.dispatchEvent(new Event("chatroom:users-updated"));
}
/**
* 合并高频在线名单变动,避免 Presence 连续进出时重复重建名单 DOM。
*/
export function scheduleRenderUserList(delay = 120) {
const state = window.chatState;
if (!state) return;
if (state.userListRenderTimer) {
window.clearTimeout(state.userListRenderTimer);
}
state.userListRenderTimer = window.setTimeout(() => {
state.userListRenderTimer = null;
renderUserList();
}, delay);
}
/**
* 搜索/过滤用户列表(仅操作 DOM 可见性,不重建)。
*/
export function filterUserList() {
const searchInput = document.getElementById("user-search-input");
const keyword = searchInput ? searchInput.value.trim().toLowerCase() : "";
const state = window.chatState;
const userList = state?.userList || document.getElementById("online-users-list");
if (!userList) return;
const items = userList.querySelectorAll(".user-item");
items.forEach((item) => {
if (!keyword) {
item.style.display = "";
return;
}
const name = (item.dataset.username || item.textContent || "").toLowerCase();
item.style.display = name.includes(keyword) ? "" : "none";
});
}
/**
* 调度用户列表搜索过滤,避免每个按键都同步扫描名单 DOM。
*/
export function scheduleFilterUserList() {
const state = window.chatState;
if (!state) return;
if (state.userFilterRenderTimer !== null) {
return;
}
const scheduleFilter = window.requestAnimationFrame || ((callback) => window.setTimeout(callback, 16));
state.userFilterRenderTimer = scheduleFilter(() => {
state.userFilterRenderTimer = null;
filterUserList();
});
}
// ── 徽标旋转定时器 ──
let badgeRotationInterval = null;
export function startBadgeRotation() {
if (badgeRotationInterval) return;
badgeRotationInterval = window.setInterval(() => {
if (document.hidden) return;
const state = window.chatState;
if (!state) return;
state.userBadgeRotationTick = (state.userBadgeRotationTick + 1) % 4;
if (state.userList) {
refreshRenderedUserBadges(state.userList);
}
const mobileUsersList = document.getElementById("mob-online-users-list");
if (mobileUsersList?.offsetParent !== null) {
refreshRenderedUserBadges(mobileUsersList);
}
// 同步每日状态 UI
if (typeof window.syncDailyStatusUi === "function") {
window.syncDailyStatusUi();
}
}, 3000);
}
export function stopBadgeRotation() {
if (badgeRotationInterval) {
window.clearInterval(badgeRotationInterval);
badgeRotationInterval = null;
}
}
// ── 挂载到 window ──
window.renderUserList = renderUserList;
window.renderUserListToContainer = renderUserListToContainer;
window.filterUserList = filterUserList;
window.scheduleFilterUserList = scheduleFilterUserList;
window.scheduleRenderUserList = scheduleRenderUserList;
window.refreshRenderedUserBadges = refreshRenderedUserBadges;
window.buildUserBadgeHtml = buildUserBadgeHtml;
window._renderUserListToContainer = renderUserListToContainer;
export {
buildUserPrimaryBadgeHtml,
buildUserVipBadgeHtml,
buildUserStatusBadgeHtml,
buildUserSignIdentityBadgeHtml,
resolveUserDailyStatus,
};