Files
LittleWhiteBox/widgets/message-toolbar.js
2026-01-18 11:44:14 +08:00

266 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// widgets/message-toolbar.js
/**
* 消息工具栏管理器
* 统一管理消息级别的功能按钮TTS、画图等
*/
let toolbarMap = new WeakMap();
const registeredComponents = new Map(); // messageId -> Map<componentId, element>
let stylesInjected = false;
function injectStyles() {
if (stylesInjected) return;
stylesInjected = true;
const style = document.createElement('style');
style.id = 'xb-msg-toolbar-styles';
style.textContent = `
.xb-msg-toolbar {
display: flex;
align-items: center;
gap: 8px;
margin: 8px 0;
min-height: 34px;
flex-wrap: wrap;
}
.xb-msg-toolbar:empty {
display: none;
}
.xb-msg-toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.xb-msg-toolbar-right {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
}
.xb-msg-toolbar-left:empty {
display: none;
}
.xb-msg-toolbar-right:empty {
display: none;
}
`;
document.head.appendChild(style);
}
function getMessageElement(messageId) {
return document.querySelector(`.mes[mesid="${messageId}"]`);
}
/**
* 获取或创建消息的工具栏
*/
export function getOrCreateToolbar(messageEl) {
if (!messageEl) return null;
// 已有工具栏且有效
if (toolbarMap.has(messageEl)) {
const existing = toolbarMap.get(messageEl);
if (existing.isConnected) return existing;
toolbarMap.delete(messageEl);
}
injectStyles();
// 找锚点
const nameBlock = messageEl.querySelector('.mes_block > .ch_name') ||
messageEl.querySelector('.name_text')?.parentElement;
if (!nameBlock) return null;
// 检查是否已有工具栏
let toolbar = nameBlock.parentNode.querySelector(':scope > .xb-msg-toolbar');
if (toolbar) {
toolbarMap.set(messageEl, toolbar);
ensureSections(toolbar);
return toolbar;
}
// 创建工具栏
toolbar = document.createElement('div');
toolbar.className = 'xb-msg-toolbar';
const leftSection = document.createElement('div');
leftSection.className = 'xb-msg-toolbar-left';
const rightSection = document.createElement('div');
rightSection.className = 'xb-msg-toolbar-right';
toolbar.appendChild(leftSection);
toolbar.appendChild(rightSection);
nameBlock.parentNode.insertBefore(toolbar, nameBlock.nextSibling);
toolbarMap.set(messageEl, toolbar);
return toolbar;
}
function ensureSections(toolbar) {
if (!toolbar.querySelector('.xb-msg-toolbar-left')) {
const left = document.createElement('div');
left.className = 'xb-msg-toolbar-left';
toolbar.insertBefore(left, toolbar.firstChild);
}
if (!toolbar.querySelector('.xb-msg-toolbar-right')) {
const right = document.createElement('div');
right.className = 'xb-msg-toolbar-right';
toolbar.appendChild(right);
}
}
/**
* 注册组件到工具栏
*/
export function registerToToolbar(messageId, element, options = {}) {
const { position = 'left', id } = options;
const messageEl = getMessageElement(messageId);
if (!messageEl) return false;
const toolbar = getOrCreateToolbar(messageEl);
if (!toolbar) return false;
// 设置组件 ID
if (id) {
element.dataset.toolbarId = id;
// 去重:移除已存在的同 ID 组件
const existing = toolbar.querySelector(`[data-toolbar-id="${id}"]`);
if (existing && existing !== element) {
existing.remove();
}
}
// 插入到对应区域
const section = position === 'right'
? toolbar.querySelector('.xb-msg-toolbar-right')
: toolbar.querySelector('.xb-msg-toolbar-left');
if (section && !section.contains(element)) {
section.appendChild(element);
}
// 记录
if (!registeredComponents.has(messageId)) {
registeredComponents.set(messageId, new Map());
}
if (id) {
registeredComponents.get(messageId).set(id, element);
}
return true;
}
/**
* 从工具栏移除组件
*/
export function removeFromToolbar(messageId, element) {
if (!element) return;
const componentId = element.dataset?.toolbarId;
element.remove();
// 清理记录
const components = registeredComponents.get(messageId);
if (components && componentId) {
components.delete(componentId);
if (components.size === 0) {
registeredComponents.delete(messageId);
}
}
cleanupEmptyToolbar(messageId);
}
/**
* 根据 ID 移除组件
*/
export function removeFromToolbarById(messageId, componentId) {
const messageEl = getMessageElement(messageId);
if (!messageEl) return;
const toolbar = toolbarMap.get(messageEl);
if (!toolbar) return;
const element = toolbar.querySelector(`[data-toolbar-id="${componentId}"]`);
if (element) {
removeFromToolbar(messageId, element);
}
}
/**
* 检查组件是否已注册
*/
export function hasComponent(messageId, componentId) {
const messageEl = getMessageElement(messageId);
if (!messageEl) return false;
const toolbar = toolbarMap.get(messageEl);
if (!toolbar) return false;
return !!toolbar.querySelector(`[data-toolbar-id="${componentId}"]`);
}
/**
* 清理空工具栏
*/
function cleanupEmptyToolbar(messageId) {
const messageEl = getMessageElement(messageId);
if (!messageEl) return;
const toolbar = toolbarMap.get(messageEl);
if (!toolbar) return;
const leftSection = toolbar.querySelector('.xb-msg-toolbar-left');
const rightSection = toolbar.querySelector('.xb-msg-toolbar-right');
const isEmpty = (!leftSection || leftSection.children.length === 0) &&
(!rightSection || rightSection.children.length === 0);
if (isEmpty) {
toolbar.remove();
toolbarMap.delete(messageEl);
}
}
/**
* 移除消息的整个工具栏
*/
export function removeToolbar(messageId) {
const messageEl = getMessageElement(messageId);
if (!messageEl) return;
const toolbar = toolbarMap.get(messageEl);
if (toolbar) {
toolbar.remove();
toolbarMap.delete(messageEl);
}
registeredComponents.delete(messageId);
}
/**
* 清理所有工具栏
*/
export function removeAllToolbars() {
document.querySelectorAll('.xb-msg-toolbar').forEach(t => t.remove());
toolbarMap = new WeakMap();
registeredComponents.clear();
}
/**
* 获取工具栏(如果存在)
*/
export function getToolbar(messageId) {
const messageEl = getMessageElement(messageId);
return messageEl ? toolbarMap.get(messageEl) : null;
}