Files
yawiii-ComfyUI-Prompt-Assis…/js/modules/tagConfigManager.js
T

1005 lines
41 KiB
JavaScript

/**
* 标签配置管理器
* 负责管理标签配置弹窗和标签数据的增删改查
*/
import { app } from "../../../../scripts/app.js";
import { logger } from '../utils/logger.js';
import { ResourceManager } from "../utils/resourceManager.js";
import {
createSettingsDialog,
closeModalWithAnimation,
createFormGroup,
createInputGroup,
createSelectGroup,
createHorizontalFormGroup
} from "./settings.js";
class TagConfigManager {
constructor() {
this.tagsData = {};
this.tooltipElements = [];
this.categorySortable = null;
this.tagSortables = new Map();
this.Sortable = null;
this.isDirty = false; // 跟踪未保存的更改
this.initialTagsData = null; // 打开时备份初始数据
}
/**
* 初始化 Sortable.js
* @private
*/
async _initSortableLib() {
if (this.Sortable) {
return;
}
try {
this.Sortable = await ResourceManager.getSortable();
} catch (error) {
logger.error('Sortable.js 加载失败,拖拽功能将不可用。', error);
app.extensionManager.toast.add({
severity: "error",
summary: "组件加载失败",
detail: "拖拽功能可能无法正常工作",
life: 3000
});
}
}
/**
* 显示标签配置弹窗
*/
async showTagsConfigModal() {
try {
await this._initSortableLib();
logger.debug('打开标签配置弹窗');
this._createTagsConfigDialog();
} catch (error) {
logger.error(`打开标签配置弹窗失败: ${error.message}`);
app.extensionManager.toast.add({
severity: "error",
summary: "打开配置失败",
detail: error.message || "打开配置弹窗过程中发生错误",
life: 3000
});
}
}
/**
* 初始化标签tooltip功能
* 为有data-tooltip-content属性的元素添加tooltip
*/
_initTooltips() {
// 清理之前的tooltip事件监听
this._cleanupTooltips();
// 查找所有带有tooltip内容属性的元素
const tooltipElements = document.querySelectorAll('.has-tooltip');
tooltipElements.forEach(element => {
// 保存引用以便清理
this.tooltipElements.push(element);
let tooltipTimeout = null;
let activeTooltip = null;
// 鼠标进入时创建tooltip
element.addEventListener('mouseenter', e => {
tooltipTimeout = setTimeout(() => {
const content = element.getAttribute('data-tooltip-content');
if (!content) return;
// 创建tooltip元素
const tooltip = document.createElement('div');
tooltip.className = 'tag_tooltip';
tooltip.innerHTML = content;
document.body.appendChild(tooltip);
// 计算位置
const rect = element.getBoundingClientRect();
tooltip.style.left = `${rect.left + rect.width / 2 - tooltip.offsetWidth / 2}px`;
tooltip.style.top = `${rect.top - tooltip.offsetHeight - 8}px`;
// 保存当前tooltip引用
activeTooltip = tooltip;
}, 300);
});
// 鼠标离开时移除tooltip
element.addEventListener('mouseleave', () => {
clearTimeout(tooltipTimeout);
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
});
});
}
/**
* 清理tooltip事件监听
*/
_cleanupTooltips() {
// 移除所有tooltip元素
const tooltips = document.querySelectorAll('.tag_tooltip');
tooltips.forEach(tooltip => tooltip.remove());
// 清空保存的元素引用
this.tooltipElements = [];
}
/**
* 加载用户标签数据
* @returns {Promise<Object>} 用户标签数据
*/
async _loadTagsData() {
try {
const response = await fetch('/prompt_assistant/api/config/tags_user');
if (!response.ok) {
throw new Error(`加载用户标签配置失败: ${response.status} ${response.statusText}`);
}
this.tagsData = await response.json();
// 确保至少有一个默认分类
if (Object.keys(this.tagsData).length === 0) {
this.tagsData = {
"默认分类": {}
};
}
// 备份初始数据并重置脏状态
this.initialTagsData = JSON.parse(JSON.stringify(this.tagsData));
this.isDirty = false;
return this.tagsData;
} catch (error) {
logger.error("加载用户标签配置失败:", error);
app.extensionManager.toast.add({
severity: "error",
summary: "加载失败",
detail: error.message || "加载用户标签配置过程中发生错误",
life: 3000
});
// 返回默认分类
this.tagsData = {
"默认分类": {}
};
// 备份初始数据并重置脏状态
this.initialTagsData = JSON.parse(JSON.stringify(this.tagsData));
this.isDirty = false;
return this.tagsData;
}
}
/**
* 保存用户标签数据
* @param {Object} data 要保存的标签数据
* @returns {Promise<boolean>} 是否保存成功
*/
async _saveTagsData(data) {
try {
const response = await fetch('/prompt_assistant/api/config/tags_user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`保存用户标签配置失败: ${response.status} ${response.statusText}`);
}
return true;
} catch (error) {
logger.error("保存用户标签配置失败:", error);
app.extensionManager.toast.add({
severity: "error",
summary: "保存失败",
detail: error.message || "保存用户标签配置过程中发生错误",
life: 3000
});
return false;
}
}
/**
* 初始化拖拽排序
* @param {HTMLElement} container 容器元素
* @param {HTMLElement} tagContainer 标签容器
* @param {string} categoryName 分类名称
* @private
*/
_initSortable(container, tagContainer, categoryName) {
if (!this.Sortable) {
return;
}
const tagSortable = new this.Sortable(tagContainer, {
group: 'tags',
animation: 250, // 增加动画时长,使过渡更平滑
easing: 'cubic-bezier(0.25, 1, 0.5, 1)', // 调整缓动函数,避免缩放效果
ghostClass: 'tag-ghost',
chosenClass: 'tag-chosen',
dragClass: 'tag-drag',
// 添加拖拽时的延迟,防止意外触发
delay: 50,
delayOnTouchOnly: true,
// 添加动画捕获状态,确保动画正确执行
onChoose: function (evt) {
// 不再修改transition,避免影响缩放
},
onUnchoose: function (evt) {
// 不再修改transition,避免影响缩放
},
// 添加动画相关事件处理
onStart: function (evt) {
// 拖动开始时添加效果
evt.item.classList.add('tag-dragging');
},
onEnd: (evt) => {
// 拖动结束时移除效果
evt.item.classList.remove('tag-dragging');
const { from, to } = evt;
const fromCategory = from.closest('.tag_accordion').getAttribute('data-category');
const toCategory = to.closest('.tag_accordion').getAttribute('data-category');
const tagName = evt.item.getAttribute('data-name');
// 从旧分类中移除(内存中)
const tagData = this.tagsData[fromCategory][tagName];
delete this.tagsData[fromCategory][tagName];
// 添加到新分类(内存中),并根据DOM顺序重构
const newTags = {};
Array.from(to.children).forEach(tag => {
const name = tag.getAttribute('data-name');
const value = tag.getAttribute('data-value');
newTags[name] = this.tagsData[toCategory]?.[name] || tagData;
});
this.tagsData[toCategory] = newTags;
this.isDirty = true;
}
});
this.tagSortables.set(categoryName, tagSortable);
}
/**
* 初始化分类排序
* @param {HTMLElement} container 容器元素
* @private
*/
_initCategorySortable(container) {
if (!this.Sortable) {
return;
}
if (this.categorySortable) {
this.categorySortable.destroy();
}
this.categorySortable = new this.Sortable(container, {
animation: 250, // 增加动画时长,使过渡更平滑
easing: 'cubic-bezier(0.25, 1, 0.5, 1)', // 调整缓动函数,避免缩放效果
handle: '.tag_accordion_header',
ghostClass: 'category-ghost',
chosenClass: 'category-chosen',
dragClass: 'category-drag',
// 添加拖拽时的延迟,防止意外触发
delay: 50,
delayOnTouchOnly: true,
// 添加动画捕获状态,确保动画正确执行
onChoose: function (evt) {
// 不再修改transition,避免影响缩放
},
onUnchoose: function (evt) {
// 不再修改transition,避免影响缩放
},
// 添加动画相关事件处理
onStart: function (evt) {
// 拖动开始时添加效果
evt.item.classList.add('category-dragging');
},
onEnd: (evt) => {
// 拖动结束时移除效果
evt.item.classList.remove('category-dragging');
// 根据DOM顺序重构数据
const newData = {};
Array.from(container.children).forEach(accordion => {
const categoryName = accordion.getAttribute('data-category');
newData[categoryName] = { ...this.tagsData[categoryName] };
});
this.tagsData = newData;
this.isDirty = true;
}
});
}
/**
* 创建标签手风琴
* @param {Object} tagsData 标签数据
* @param {HTMLElement} container 容器元素
*/
_createTagAccordion(tagsData, container) {
// 清空容器
container.innerHTML = '';
// 遍历所有分类,使用Object.entries保持原始顺序
Object.entries(tagsData).forEach(([categoryName, categoryTags]) => {
// 创建分类手风琴
const accordion = document.createElement('div');
accordion.className = 'tag_accordion';
accordion.setAttribute('data-category', categoryName);
// 创建头部
const header = document.createElement('div');
header.className = 'tag_accordion_header active'; // 默认设置为active状态
// 创建标题
const title = document.createElement('div');
title.className = 'tag_accordion_title';
title.textContent = categoryName;
// 创建图标容器
const iconContainer = document.createElement('div');
iconContainer.className = 'tag_accordion_icon';
// 添加编辑按钮 - 为所有分类添加
const editButton = document.createElement('button');
editButton.className = 'tag_edit_button small'; // 使用新的编辑按钮样式
editButton.title = '编辑分类';
const editIcon = document.createElement('span');
editIcon.className = 'pi pi-pencil tag_edit_icon small'; // 使用新的编辑图标样式
editButton.appendChild(editIcon);
editButton.addEventListener('click', (e) => {
e.stopPropagation();
this._showCategoryDialog(container, categoryName);
});
iconContainer.appendChild(editButton);
// 添加删除按钮
const deleteButton = document.createElement('button');
deleteButton.className = 'tag_delete_button small'; // 添加small类名
deleteButton.title = '删除分类';
// 添加删除图标
const deleteIcon = document.createElement('span');
deleteIcon.className = 'pi pi-times tag_action_icon small';
deleteButton.appendChild(deleteIcon);
// 添加删除事件
deleteButton.addEventListener('click', (e) => {
e.stopPropagation();
// 创建确认对话框
createSettingsDialog({
title: '确认删除',
isConfirmDialog: true,
disableBackdropAndCloseOnClickOutside: true,
saveButtonText: '确认',
cancelButtonText: '取消',
renderContent: (content) => {
content.style.textAlign = 'center';
content.style.padding = '1rem';
const confirmMessage = document.createElement('p');
confirmMessage.textContent = `确定要删除分类"${categoryName}"吗?`;
confirmMessage.style.margin = '0';
confirmMessage.style.fontSize = '1rem';
content.appendChild(confirmMessage);
},
onSave: () => {
// 删除分类
logger.debug(`正在尝试删除分类: "${categoryName}"`);
if (this.tagsData[categoryName]) {
delete this.tagsData[categoryName];
this.isDirty = true;
logger.debug(`分类"${categoryName}"已删除,重新渲染界面`);
// 确保重新渲染使用最新的数据
setTimeout(() => {
this._createTagAccordion(this.tagsData, container);
this._initTooltips(); // 重新初始化tooltips
}, 0);
} else {
logger.warn(`无法找到分类: ${categoryName}`);
}
return true; // 确保弹窗关闭
}
});
});
iconContainer.appendChild(deleteButton);
// 添加箭头图标(移到最后)
const arrowIcon = document.createElement('span');
arrowIcon.className = 'pi pi-chevron-down accordion_arrow_icon rotate-180'; // 默认旋转箭头
arrowIcon.style.fontSize = '12px'; // 设置图标大小
iconContainer.appendChild(arrowIcon);
header.appendChild(title);
header.appendChild(iconContainer);
// 添加内容区域
const content = document.createElement('div');
content.className = 'tag_accordion_content active'; // 默认设置为active状态
content.style.maxHeight = 'none'; // 默认展开
// 添加标签列表
const tagContainer = document.createElement('div');
tagContainer.className = 'tags_container';
// 添加标签
for (const [tagName, tagValue] of Object.entries(categoryTags)) {
const tagItem = document.createElement('div');
tagItem.className = 'tag_item';
tagItem.setAttribute('data-name', tagName);
tagItem.setAttribute('data-value', tagValue);
// 添加标签文本
const tagText = document.createElement('span');
tagText.className = 'tag_item_text';
tagText.textContent = tagName;
// 添加编辑按钮
const editButton = document.createElement('button');
editButton.className = 'tag_edit_button small'; // 使用新的编辑按钮样式
editButton.title = '编辑标签';
const editIcon = document.createElement('span');
editIcon.className = 'pi pi-pencil tag_edit_icon small'; // 使用新的编辑图标样式
editButton.appendChild(editIcon);
editButton.addEventListener('click', (e) => {
e.stopPropagation();
this._showTagDialog(container, categoryName, tagName);
});
// 添加删除按钮
const deleteButton = document.createElement('button');
deleteButton.className = 'tag_delete_button small';
deleteButton.title = '删除标签';
// 添加删除图标
const deleteIcon = document.createElement('span');
deleteIcon.className = 'pi pi-times tag_action_icon small';
deleteButton.appendChild(deleteIcon);
// 添加删除事件
deleteButton.addEventListener('click', (e) => {
e.stopPropagation();
const thisName = tagName; // 保存当前的tagName,避免在闭包中引用循环变量
const thisCategory = categoryName; // 保存当前的categoryName
// 创建确认对话框
createSettingsDialog({
title: '确认删除',
isConfirmDialog: true,
disableBackdropAndCloseOnClickOutside: true,
saveButtonText: '确认',
cancelButtonText: '取消',
renderContent: (content) => {
content.style.textAlign = 'center';
content.style.padding = '1rem';
const confirmMessage = document.createElement('p');
confirmMessage.textContent = `确定要删除标签"${thisName}"吗?`;
confirmMessage.style.margin = '0';
confirmMessage.style.fontSize = '1rem';
content.appendChild(confirmMessage);
},
onSave: () => {
// 删除标签
logger.debug(`正在尝试删除标签: "${thisName}" (分类: "${thisCategory}")`);
if (this.tagsData[thisCategory] && this.tagsData[thisCategory][thisName]) {
delete this.tagsData[thisCategory][thisName];
this.isDirty = true;
logger.debug(`标签 "${thisName}" 已从分类 "${thisCategory}" 中删除,重新渲染界面`);
// 确保重新渲染使用最新的数据
setTimeout(() => {
this._createTagAccordion(this.tagsData, container);
this._initTooltips(); // 重新初始化tooltips
}, 0);
} else {
logger.warn(`无法找到标签: ${thisName} 在分类 ${thisCategory} 中`);
}
return true; // 确保弹窗关闭
}
});
});
// 添加tooltip功能
tagItem.title = tagValue;
tagItem.setAttribute('data-tooltip-content', `<span class="tooltip_path">${tagValue}</span>`);
tagItem.classList.add('has-tooltip');
tagItem.appendChild(tagText);
tagItem.appendChild(editButton);
tagItem.appendChild(deleteButton);
tagContainer.appendChild(tagItem);
}
content.appendChild(tagContainer);
// 添加手风琴切换事件
header.addEventListener('click', (e) => {
if (e.target.closest('.tag_delete_button')) return;
const isActive = !header.classList.contains('active');
header.classList.toggle('active');
content.classList.toggle('active');
// 添加过渡效果
if (isActive) {
// 先设置一个固定高度,确保动画效果
content.style.maxHeight = content.scrollHeight + 'px';
arrowIcon.classList.add('rotate-180');
// 处理滚动容器定位,确保展开内容可见
setTimeout(() => {
// 计算手风琴的位置
const accordionTop = accordion.offsetTop;
const scrollTop = container.closest('.tags-scroll-container').scrollTop;
const containerHeight = container.closest('.tags-scroll-container').clientHeight;
// 如果手风琴底部超出可视区域,滚动到合适位置
const accordionBottom = accordionTop + header.offsetHeight + content.scrollHeight - scrollTop;
if (accordionBottom > containerHeight) {
// 确保手风琴标题仍在可视区域顶部
container.closest('.tags-scroll-container').scrollTo({
top: Math.max(accordionTop - 20, 0),
behavior: 'smooth'
});
}
}, 50);
} else {
content.style.maxHeight = '0';
arrowIcon.classList.remove('rotate-180');
}
});
// 默认所有手风琴都是展开状态
header.classList.add('active');
content.classList.add('active');
content.style.maxHeight = 'none'; // 默认不限制高度,完全展开
content.style.overflow = 'visible'; // 确保内容可见
arrowIcon.classList.add('rotate-180'); // 箭头朝上表示展开状态
accordion.appendChild(header);
accordion.appendChild(content);
container.appendChild(accordion);
// 初始化标签排序
this._initSortable(container, tagContainer, categoryName);
});
// 初始化分类排序
this._initCategorySortable(container);
}
/**
* 创建标签配置弹窗
* @private
*/
_createTagsConfigDialog() {
// 创建一个变量来存储原始的isDirty值
const originalIsDirty = this.isDirty;
// 创建一个变量来存储表单修改状态设置函数
let setFormModified = null;
const dialog = createSettingsDialog({
title: '标签管理器',
dialogClassName: 'tags-config-dialog',
disableBackdropAndCloseOnClickOutside: true,
renderContent: (container) => {
// 弹窗容器的整体设置
container.className += ' tags-config-container';
container.style.width = '100%';
// 创建表单
const form = document.createElement('form');
form.onsubmit = (e) => e.preventDefault();
form.className = 'tags-config-form';
form.style.width = '100%';
// 创建一个隐藏的输入字段,用于标记表单修改状态
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'modified_marker';
hiddenInput.value = 'initial';
form.appendChild(hiddenInput);
// 定义表单修改状态设置函数
setFormModified = () => {
// 修改隐藏字段的值,这会触发表单变化检测
hiddenInput.value = 'modified_' + Date.now();
// 触发change事件
hiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
};
// 覆盖isDirty的setter,在设置为true时触发表单修改状态
Object.defineProperty(this, 'isDirty', {
get: function () {
return this._isDirty;
},
set: function (value) {
// 保存值
this._isDirty = value;
// 如果设置为true,则标记表单已修改
if (value && setFormModified) {
setFormModified();
}
}
});
// 初始化_isDirty
this._isDirty = originalIsDirty;
// 创建标签管理部分
const tagsSection = createFormGroup('标签管理');
tagsSection.className += ' tag-management-section';
tagsSection.style.width = '100%';
const tagsHeader = tagsSection.querySelector('.settings-form-section-header');
// 添加操作按钮容器 - 使用透明容器
const actionContainer = document.createElement('div');
actionContainer.className = 'tags-action-container';
actionContainer.style.display = 'flex';
actionContainer.style.gap = '8px';
// 添加分类按钮
const addCategoryButton = document.createElement('button');
addCategoryButton.className = 'p-button p-component p-button-secondary p-button-sm settings-action-btn';
addCategoryButton.title = '添加分类';
addCategoryButton.innerHTML = '<span class="p-button-icon p-button-icon-left pi pi-plus"></span><span class="p-button-label">添加分类</span>';
addCategoryButton.onclick = () => this._showCategoryDialog(tagsContainer);
// 添加标签按钮
const addTagButton = document.createElement('button');
addTagButton.className = 'p-button p-component p-button-secondary p-button-sm settings-action-btn';
addTagButton.title = '添加标签';
addTagButton.innerHTML = '<span class="p-button-icon p-button-icon-left pi pi-tag"></span><span class="p-button-label">添加标签</span>';
addTagButton.onclick = () => this._showTagDialog(tagsContainer);
// 添加按钮到容器
actionContainer.appendChild(addCategoryButton);
actionContainer.appendChild(addTagButton);
tagsHeader.appendChild(actionContainer);
// 创建标签容器
const tagsContainer = document.createElement('div');
tagsContainer.className = 'tags-manager-container';
tagsContainer.style.width = '100%';
// 创建一个固定高度的滚动容器
const scrollContainer = document.createElement('div');
scrollContainer.className = 'tags-scroll-container';
scrollContainer.style.width = '100%';
scrollContainer.appendChild(tagsContainer);
tagsSection.appendChild(scrollContainer);
// 加载标签数据并渲染
this._loadTagsData().then(data => {
this._createTagAccordion(data, tagsContainer);
// 初始化tooltip功能
setTimeout(() => this._initTooltips(), 100);
});
form.appendChild(tagsSection);
container.appendChild(form);
},
onSave: async (content) => {
// 如果没有更改,直接关闭
if (!this.isDirty) {
return true;
}
// 保存标签数据
try {
const success = await this._saveTagsData(this.tagsData);
if (success) {
// 保存成功后,强制刷新用户标签缓存
ResourceManager.getUserTagData(true);
// 更新初始数据并重置脏状态
this.initialTagsData = JSON.parse(JSON.stringify(this.tagsData));
this.isDirty = false;
app.extensionManager.toast.add({
severity: "success",
summary: "标签配置已更新",
detail: "用户自定义标签已保存",
life: 3000
});
// 恢复原始属性定义
if (Object.getOwnPropertyDescriptor(this, 'isDirty').set) {
delete this.isDirty;
this.isDirty = this._isDirty;
delete this._isDirty;
}
return true;
} else {
throw new Error("保存失败");
}
} catch (error) {
app.extensionManager.toast.add({
severity: "error",
summary: "保存失败",
detail: error.message || "标签保存过程中发生错误",
life: 3000
});
throw error;
}
},
onCancel: () => {
// 如果有未保存的更改,通用确认对话框会自动处理
if (this.isDirty) {
// 恢复初始数据
this.tagsData = JSON.parse(JSON.stringify(this.initialTagsData));
this.isDirty = false;
}
// 清理tooltips资源
this._cleanupTooltips();
// 恢复原始属性定义
if (Object.getOwnPropertyDescriptor(this, 'isDirty').set) {
delete this.isDirty;
this.isDirty = this._isDirty;
delete this._isDirty;
}
return true; // 允许关闭
}
});
return dialog;
}
/**
* 显示分类对话框(添加/编辑)
* @param {HTMLElement} container 标签容器
* @param {string} [oldCategoryName] 如果是编辑模式,则传入旧的分类名
*/
_showCategoryDialog(container, oldCategoryName = null) {
const isEdit = !!oldCategoryName;
createSettingsDialog({
title: isEdit ? '编辑分类' : '添加分类',
isConfirmDialog: true,
disableBackdropAndCloseOnClickOutside: true,
saveButtonText: isEdit ? '保存' : '添加',
cancelButtonText: '取消',
renderContent: (content) => {
content.className += ' dialog-form-content';
content.style.padding = '1rem';
const categoryInput = createInputGroup('分类名称', '请输入分类名称');
categoryInput.group.style.marginBottom = '0';
if (isEdit) {
categoryInput.input.value = oldCategoryName;
}
content.appendChild(categoryInput.group);
// 聚焦输入框
setTimeout(() => {
categoryInput.input.focus();
}, 100);
content.categoryInput = categoryInput.input;
},
onSave: (content) => {
const categoryName = content.categoryInput.value.trim();
if (!categoryName) {
app.extensionManager.toast.add({
severity: "error",
summary: isEdit ? "编辑失败" : "添加失败",
detail: "分类名称不能为空",
life: 3000
});
return;
}
// 检查是否已存在(排除编辑时的自身)
if (!isEdit && this.tagsData[categoryName] ||
(isEdit && categoryName !== oldCategoryName && this.tagsData[categoryName])) {
app.extensionManager.toast.add({
severity: "error",
summary: isEdit ? "编辑失败" : "添加失败",
detail: "该分类已存在",
life: 3000
});
return;
}
if (isEdit && categoryName !== oldCategoryName) {
// 创建一个新的对象来保持顺序
const newData = {};
// 遍历现有数据,在适当的位置替换分类名
Object.entries(this.tagsData).forEach(([name, content]) => {
if (name === oldCategoryName) {
newData[categoryName] = content;
} else {
newData[name] = content;
}
});
this.tagsData = newData;
} else if (!isEdit) {
// 添加新分类
this.tagsData[categoryName] = {};
}
this.isDirty = true;
this._createTagAccordion(this.tagsData, container);
app.extensionManager.toast.add({
severity: "success",
summary: isEdit ? "编辑成功" : "添加成功",
detail: isEdit ? `已更新分类"${categoryName}"` : `已添加分类"${categoryName}"`,
life: 3000
});
return true;
}
});
}
/**
* 显示标签对话框(添加/编辑)
* @param {HTMLElement} container 标签容器
* @param {string} [categoryName] 如果是编辑模式,则传入分类名
* @param {string} [oldTagName] 如果是编辑模式,则传入旧的标签名
*/
_showTagDialog(container, categoryName = null, oldTagName = null) {
const isEdit = !!oldTagName;
const oldTagValue = isEdit ? this.tagsData[categoryName][oldTagName] : '';
createSettingsDialog({
title: isEdit ? '编辑标签' : '添加标签',
isConfirmDialog: true,
disableBackdropAndCloseOnClickOutside: true,
saveButtonText: isEdit ? '保存' : '添加',
cancelButtonText: '取消',
renderContent: (content) => {
content.className += ' dialog-form-content';
content.style.padding = '1rem';
// 创建分类选择器(编辑和添加模式都显示)
const categories = Object.keys(this.tagsData).map(cat => ({ value: cat, text: cat }));
const categorySelect = createSelectGroup('选择分类', categories, isEdit ? categoryName : null);
categorySelect.group.style.marginBottom = '10px';
content.appendChild(categorySelect.group);
content.categorySelect = categorySelect.select;
// 创建标签名称输入框
const tagNameInput = createInputGroup('标签名称', '请输入标签显示名称');
tagNameInput.group.style.marginBottom = '10px';
if (isEdit) {
tagNameInput.input.value = oldTagName;
}
// 创建标签内容输入框
const tagValueInput = createInputGroup('标签内容', '请输入标签插入内容');
tagValueInput.group.style.marginBottom = '0';
if (isEdit) {
tagValueInput.input.value = oldTagValue;
}
content.appendChild(tagNameInput.group);
content.appendChild(tagValueInput.group);
content.tagNameInput = tagNameInput.input;
content.tagValueInput = tagValueInput.input;
// 聚焦标签名称输入框
setTimeout(() => {
tagNameInput.input.focus();
}, 100);
},
onSave: (content) => {
const targetCategory = content.categorySelect.value;
const tagName = content.tagNameInput.value.trim();
const tagValue = content.tagValueInput.value.trim();
if (!targetCategory || !tagName || !tagValue) {
app.extensionManager.toast.add({
severity: "error",
summary: isEdit ? "编辑失败" : "添加失败",
detail: "分类、标签名称和标签内容不能为空",
life: 3000
});
return;
}
// 检查标签是否已存在
const isSameCategory = isEdit && targetCategory === categoryName;
const isTagNameChanged = isEdit && tagName !== oldTagName;
// 在以下情况下检查重复:
// 1. 添加新标签时
// 2. 编辑时改变了标签名(在同一分类内)
// 3. 移动到其他分类且目标分类已有同名标签
if ((!isEdit && this.tagsData[targetCategory][tagName]) ||
(isEdit && (
(isSameCategory && isTagNameChanged && this.tagsData[targetCategory][tagName]) ||
(!isSameCategory && this.tagsData[targetCategory][tagName])
))) {
app.extensionManager.toast.add({
severity: "error",
summary: isEdit ? "编辑失败" : "添加失败",
detail: `标签"${tagName}"已存在于分类"${targetCategory}"中`,
life: 3000
});
return;
}
if (isEdit) {
// 如果分类发生了更改,我们需要从旧分类中移除标签
if (targetCategory !== categoryName) {
const originalCategoryTags = {};
Object.entries(this.tagsData[categoryName]).forEach(([name, value]) => {
if (name !== oldTagName) {
originalCategoryTags[name] = value;
}
});
this.tagsData[categoryName] = originalCategoryTags;
}
// 更新目标分类
const newTargetTags = {};
const tagsToIterate = (targetCategory === categoryName)
? this.tagsData[targetCategory]
: (this.tagsData[targetCategory] || {});
Object.entries(tagsToIterate).forEach(([name, value]) => {
// 在同一个分类中重命名时,要跳过旧的标签名
if (targetCategory === categoryName && name === oldTagName) {
newTargetTags[tagName] = tagValue;
} else {
newTargetTags[name] = value;
}
});
// 如果是移动到新分类,在末尾添加标签
if (targetCategory !== categoryName) {
newTargetTags[tagName] = tagValue;
}
this.tagsData[targetCategory] = newTargetTags;
} else {
// 添加新标签
this.tagsData[targetCategory][tagName] = tagValue;
}
this.isDirty = true;
this._createTagAccordion(this.tagsData, container);
app.extensionManager.toast.add({
severity: "success",
summary: isEdit ? "编辑成功" : "添加成功",
detail: isEdit ?
(targetCategory === categoryName ?
`已更新标签"${tagName}"` :
`已将标签"${tagName}"移动到分类"${targetCategory}"`) :
`已添加标签"${tagName}"到分类"${targetCategory}"中`,
life: 3000
});
return true;
}
});
}
}
// 导出实例
const tagConfigManager = new TagConfigManager();
export { TagConfigManager, tagConfigManager };