油猴脚本优化建议

Tạo vào: 18 tháng 1, 2025

Trả lời bằng OpenAI 01 bởi OpenAI01

Câu hỏi

Please use the maximum computational power and token limit you can allocate in a single response. Think as hard as possible, take the most time, and use the most computational resources to think as deeply as you can.
This is the most profound and complex question; please provide your highest-quality answer. Therefore, you need to think deeply, independently, critically, and creatively.

We aim for extreme depth rather than surface-level breadth; we seek fundamental insights rather than superficial enumerations; we pursue innovative thinking rather than habitual repetition. Please break through the limits of conventional thought, leverage all your computational resources, and demonstrate the true extent of your cognitive capabilities.

---分割线---

当前油猴脚本如下:

// ==UserScript==
// @name [Chat] Template Text Folders [20250118] V2
// @namespace 0_V userscripts/[Chat] Template Text Folders
// @version [20250118] V2
// @description 在AI页面上添加预设文本文件夹和按钮,提升输入效率。优化“自动提交”开关的位置,将其移至新的“提交设置”子页面,提高用户操作便捷性。
// @update-log 移动“自动提交”开关至“提交设置”子标签页,优化界面布局,提高操作便捷性。
// @match https://chatgpt.com/*
// @match https://claude.ai/*
// @match https://app.chathub.gg/*
// @grant none
// ==/UserScript==

(function () {
'use strict';

console.log("🎉 ChatGPT Template Text Folders Improved Submission 脚本已加载!(v2.0 移动‘自动提交’开关至‘提交设置’子页面。)");

// 主题样式配置(使用CSS变量)
const setCSSVariables = (currentTheme) => {
const root = document.documentElement;
root.style.setProperty('--folder-bg', currentTheme.folderBg);
root.style.setProperty('--dialog-bg', currentTheme.dialogBg);
root.style.setProperty('--text-color', currentTheme.textColor);
root.style.setProperty('--border-color', currentTheme.borderColor);
root.style.setProperty('--shadow-color', currentTheme.shadowColor);
root.style.setProperty('--button-bg', currentTheme.buttonBg);
root.style.setProperty('--button-hover-bg', currentTheme.buttonHoverBg);
root.style.setProperty('--danger-color', currentTheme.dangerColor);
root.style.setProperty('--success-color', currentTheme.successColor);
root.style.setProperty('--add-color', currentTheme.addColor);
root.style.setProperty('--primary-color', currentTheme.primaryColor);
root.style.setProperty('--info-color', currentTheme.infoColor);
root.style.setProperty('--cancel-color', currentTheme.cancelColor);
root.style.setProperty('--overlay-bg', currentTheme.overlayBg || 'rgba(0, 0, 0, 0.5)');
root.style.setProperty('--tab-bg', currentTheme.tabBg);
root.style.setProperty('--tab-active-bg', currentTheme.tabActiveBg);
root.style.setProperty('--tab-hover-bg', currentTheme.tabHoverBg);
root.style.setProperty('--tab-border', currentTheme.tabBorder);
};

const theme = {
light: {
folderBg: 'rgba(255, 255, 255, 0.8)',
dialogBg: '#ffffff',
textColor: '#333333',
borderColor: '#e5e7eb',
shadowColor: 'rgba(0, 0, 0, 0.1)',
buttonBg: '#f3f4f6',
buttonHoverBg: '#e5e7eb',
dangerColor: '#ef4444',
successColor: '#22c55e',
addColor: '#fd7e14',
primaryColor: '#3B82F6',
infoColor: '#6366F1',
cancelColor: '#6B7280',
overlayBg: 'rgba(0, 0, 0, 0.5)',
tabBg: '#f3f4f6',
tabActiveBg: '#3B82F6',
tabHoverBg: '#e5e7eb',
tabBorder: '#e5e7eb'
},
dark: {
folderBg: 'rgba(31, 41, 55, 0.8)',
dialogBg: '#1f2937',
textColor: '#e5e7eb',
borderColor: '#374151',
shadowColor: 'rgba(0, 0, 0, 0.3)',
buttonBg: '#374151',
buttonHoverBg: '#4b5563',
dangerColor: '#dc2626',
successColor: '#16a34a',
addColor: '#fd7e14',
primaryColor: '#2563EB',
infoColor: '#4F46E5',
cancelColor: '#4B5563',
overlayBg: 'rgba(0, 0, 0, 0.7)',
tabBg: '#374151',
tabActiveBg: '#2563EB',
tabHoverBg: '#4b5563',
tabBorder: '#4b5563'
}
};

const isDarkMode = () => window.matchMedia('(prefers-color-scheme: dark)').matches;
const getCurrentTheme = () => isDarkMode() ? theme.dark : theme.light;

setCSSVariables(getCurrentTheme());

const styles = {
overlay: {
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'var(--overlay-bg, rgba(0, 0, 0, 0.5))',
backdropFilter: 'blur(2px)',
zIndex: 10000,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
transition: 'background-color 0.3s ease, opacity 0.3s ease'
},
dialog: {
position: 'relative',
backgroundColor: 'var(--dialog-bg, #ffffff)',
color: 'var(--text-color, #333333)',
borderRadius: '4px',
padding: '24px',
boxShadow: '0 8px 24px var(--shadow-color, rgba(0,0,0,0.1))',
border: '1px solid var(--border-color, #e5e7eb)',
transition: 'transform 0.3s ease, opacity 0.3s ease',
maxWidth: '90vw',
maxHeight: '80vh',
overflow: 'auto'
},
button: {
padding: '8px 16px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
transition: 'background-color 0.2s ease, color 0.2s ease',
fontSize: '14px',
fontWeight: '500',
backgroundColor: 'var(--button-bg, #f3f4f6)',
color: 'var(--text-color, #333333)'
}
};

// 默认按钮
const userProvidedButtons = {
"Review": {
type: "template",
text: "You are a code review expert:\n\n{clipboard}\n\nProvide constructive feedback and improvements.\n",
color: "#E6E0FF",
textColor: "#333333",
autoSubmit: false // 新增字段
},
// ... (其他默认按钮保持不变)
"解释": {
type: "template",
text: "Explain the following code concisely:\n\n{clipboard}\n\nFocus on key functionality and purpose.\n",
color: "#ffebcc",
textColor: "#333333",
autoSubmit: false // 新增字段
}
};

// 默认工具按钮
const defaultToolButtons = {
"剪切": {
type: "tool",
action: "cut",
color: "#FFC1CC",
textColor: "#333333"
},
"复制": {
type: "tool",
action: "copy",
color: "#C1FFD7",
textColor: "#333333"
},
"粘贴": {
type: "tool",
action: "paste",
color: "#C1D8FF",
textColor: "#333333"
},
"清空": {
type: "tool",
action: "clear",
color: "#FFD1C1",
textColor: "#333333"
}
};

// 默认配置
const defaultConfig = {
folders: {
"默认": {
color: "#3B82F6",
textColor: "#ffffff",
buttons: userProvidedButtons
},
"🖱️": {
color: "#FFD700", // 金色,可根据需求调整
textColor: "#ffffff",
buttons: defaultToolButtons
}
},
folderOrder: ["默认", "🖱️"],

// ===== [新增开始] =====
// 新增字段,用于存储不同域名对应的自动提交方式
domainAutoSubmitSettings: [
{
domain: "chatgpt.com",
name: "ChatGPT",
method: "模拟点击提交按钮"
},
{
domain: "chathub.gg",
name: "ChatHub",
method: "Enter"
}
]
// ===== [新增结束] =====
};

let buttonConfig = JSON.parse(localStorage.getItem('chatGPTButtonFoldersConfig')) || JSON.parse(JSON.stringify(defaultConfig));

// ===== [新增开始] =====
// 若本地无此字段,则初始化
if (!buttonConfig.domainAutoSubmitSettings) {
buttonConfig.domainAutoSubmitSettings = JSON.parse(
JSON.stringify(defaultConfig.domainAutoSubmitSettings)
);
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
}
// ===== [新增结束] =====

// 确保所有按钮都有'type'字段
const ensureButtonTypes = () => {
let updated = false;
Object.entries(buttonConfig.folders).forEach(([folderName, folderConfig]) => {
Object.entries(folderConfig.buttons).forEach(([btnName, btnConfig]) => {
if (!btnConfig.type) {
if (folderName === "🖱️") {
btnConfig.type = "tool";
updated = true;
} else {
btnConfig.type = "template";
updated = true;
}
}
// 确保 'autoSubmit' 字段存在,对于模板按钮
if (btnConfig.type === "template" && typeof btnConfig.autoSubmit !== 'boolean') {
btnConfig.autoSubmit = false;
updated = true;
}
});
});
if (updated) {
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
console.log("✅ 已确保所有按钮具有'type'字段和'autoSubmit'配置。");
}
};
ensureButtonTypes();

// 确保工具文件夹存在并包含必要的工具按钮
const ensureToolFolder = () => {
const toolFolderName = "🖱️";
if (!buttonConfig.folders[toolFolderName]) {
buttonConfig.folders[toolFolderName] = {
color: "#FFD700",
textColor: "#ffffff",
buttons: defaultToolButtons
};
buttonConfig.folderOrder.push(toolFolderName);
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
console.log(`✅ 工具文件夹 "${toolFolderName}" 已添加到配置中。`);
} else {
// 确保工具按钮存在
Object.entries(defaultToolButtons).forEach(([btnName, btnCfg]) => {
if (!buttonConfig.folders[toolFolderName].buttons[btnName]) {
buttonConfig.folders[toolFolderName].buttons[btnName] = btnCfg;
console.log(`✅ 工具按钮 "${btnName}" 已添加到文件夹 "${toolFolderName}"。`);
}
});
}
};
ensureToolFolder();

// 变量:防止重复提交
let isSubmitting = false;

const getAllTextareas = (root = document) => {
let textareas = [];
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);
let node = walker.nextNode();
while (node) {
if (node.tagName && (node.tagName.toLowerCase() === 'textarea' || node.getAttribute('contenteditable') === 'true')) {
textareas.push(node);
}
if (node.shadowRoot) {
textareas = textareas.concat(getAllTextareas(node.shadowRoot));
}
node = walker.nextNode();
}
return textareas;
};

/**
* 插入文本到textarea或contenteditable元素中,保持换行符
* @param {HTMLElement} target - 目标元素
* @param {string} finalText - 要插入的文本
* @param {boolean} replaceAll - 是否替换全部内容(用于 {inputBoard} 和 {clipboard} 变量)
*/
const insertTextSmart = (target, finalText, replaceAll = false) => {
const normalizedText = finalText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
if (target.tagName.toLowerCase() === 'textarea') {
if (replaceAll) {
const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
nativeSetter.call(target, normalizedText);
target.selectionStart = target.selectionEnd = normalizedText.length;
const inputEvent = new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertReplacementText',
data: normalizedText,
});
target.dispatchEvent(inputEvent);
} else {
const start = target.selectionStart;
const end = target.selectionEnd;
const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
nativeSetter.call(target, target.value.substring(0, start) + normalizedText + target.value.substring(end));
target.selectionStart = target.selectionEnd = start + normalizedText.length;
const inputEvent = new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: normalizedText,
});
target.dispatchEvent(inputEvent);
}
target.focus();
} else if (target.getAttribute('contenteditable') === 'true') {
if (replaceAll) {
target.innerHTML = '';
const fragment = document.createDocumentFragment();
const lines = finalText.split('\n');
lines.forEach((line, index) => {
if (line === '') {
fragment.appendChild(document.createElement('br'));
} else {
const textNode = document.createTextNode(line);
fragment.appendChild(textNode);
if (index < lines.length - 1) {
fragment.appendChild(document.createElement('br'));
}
}
});
target.appendChild(fragment);
const range = document.createRange();
const sel = window.getSelection();
range.selectNodeContents(target);
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
} else {
const sel = window.getSelection();
if (sel.rangeCount > 0) {
const range = sel.getRangeAt(0);
range.deleteContents();
const fragment = document.createDocumentFragment();
const lines = finalText.split('\n');
lines.forEach((line, index) => {
if (line === '') {
fragment.appendChild(document.createElement('br'));
} else {
fragment.appendChild(document.createTextNode(line));
if (index < lines.length - 1) {
fragment.appendChild(document.createElement('br'));
}
}
});
range.insertNode(fragment);
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
}
}
target.focus();
}
};

/**
* 轮询检测输入框内容是否与预期文本一致。
* @param {HTMLElement} element - 要检测的textarea或contenteditable元素。
* @param {string} expectedText - 期望出现的文本。
* @param {number} interval - 轮询时间间隔(毫秒)。
* @param {number} maxWait - 最大等待时长(毫秒),超时后reject。
* @returns {Promise<void>} - 匹配成功resolve,否则reject。
*/
async function waitForContentMatch(element, expectedText, interval = 100, maxWait = 3000) {
return new Promise((resolve, reject) => {
let elapsed = 0;
const timer = setInterval(() => {
elapsed += interval;
const currentVal = (element.tagName.toLowerCase() === 'textarea')
? element.value
: element.innerText; // contenteditable时用innerText

if (currentVal === expectedText) {
clearInterval(timer);
resolve();
} else if (elapsed >= maxWait) {
clearInterval(timer);
reject(new Error("waitForContentMatch: 超时,输入框内容未能匹配预期文本"));
}
}, interval);
});
}

// 定义等待提交按钮的函数
const waitForSubmitButton = async (maxAttempts = 10, delay = 300) => {
for (let i = 0; i < maxAttempts; i++) {
const submitButton = document.querySelector('button[type="submit"], button[data-testid="send-button"]');
if (submitButton && !submitButton.disabled && submitButton.offsetParent !== null) {
return submitButton;
}
await new Promise(resolve => setTimeout(resolve, delay));
}
return null;
};

// 定义等待时间和尝试次数
const SUBMIT_WAIT_MAX_ATTEMPTS = 10;
const SUBMIT_WAIT_DELAY = 300; // 毫秒

// 在submitForm之前或附近位置新增
// ===== [新增开始] =====
function simulateEnterKey() {
const eventInit = {
bubbles: true,
cancelable: true,
key: "Enter",
code: "Enter",
keyCode: 13,
which: 13
};
const keyboardEvent = new KeyboardEvent('keydown', eventInit);
document.activeElement.dispatchEvent(keyboardEvent);
}

function simulateCmdEnterKey() {
const eventInit = {
bubbles: true,
cancelable: true,
key: "Enter",
code: "Enter",
keyCode: 13,
which: 13,
metaKey: true
};
const keyboardEvent = new KeyboardEvent('keydown', eventInit);
document.activeElement.dispatchEvent(keyboardEvent);
}
// ===== [新增结束] =====

// 定义多种提交方式
const submitForm = async () => {
if (isSubmitting) {
console.warn("⚠️ 提交正在进行中,跳过重复提交。");
return false;
}
isSubmitting = true;
try {
// ===== [新增逻辑开始] =====
const domainRules = buttonConfig.domainAutoSubmitSettings || [];
const currentURL = window.location.href;
const matchedRule = domainRules.find(rule => currentURL.includes(rule.domain));

if (matchedRule) {
console.log("🔎 检测到本域名匹配的自动提交规则:", matchedRule);
switch (matchedRule.method) {
case "Enter":
simulateEnterKey();
isSubmitting = false;
return true;
case "Cmd+Enter":
simulateCmdEnterKey();
isSubmitting = false;
return true;
case "模拟点击提交按钮":
const submitButton = await waitForSubmitButton(SUBMIT_WAIT_MAX_ATTEMPTS, SUBMIT_WAIT_DELAY);
if (submitButton) {
submitButton.click();
console.log("✅ 已根据自动化规则,模拟点击提交按钮。");
isSubmitting = false;
return true;
} else {
console.warn("⚠️ 未找到提交按钮,进入fallback...");
}
break;
default:
console.warn("⚠️ 未知自动提交方式,进入fallback...");
break;
}
}
// ===== [新增逻辑结束] =====
//
// 1. 尝试键盘快捷键提交
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const submitKeys = isMac ? ['Enter', 'Meta+Enter'] : ['Enter', 'Control+Enter'];
for (const keyCombo of submitKeys) {
const [key, modifier] = keyCombo.split('+');
const eventInit = {
bubbles: true,
cancelable: true,
key: key,
code: key,
keyCode: key.charCodeAt(0),
which: key.charCodeAt(0),
};
if (modifier === 'Meta') eventInit.metaKey = true;
if (modifier === 'Control') eventInit.ctrlKey = true;
const keyboardEvent = new KeyboardEvent('keydown', eventInit);
document.activeElement.dispatchEvent(keyboardEvent);
console.log(`尝试通过键盘快捷键提交表单:${keyCombo}`);
// 等待短暂时间,查看是否提交成功
await new Promise(resolve => setTimeout(resolve, 500));
// 检查是否页面已提交(可以通过某种标识来确认)
// 这里假设页面会有某种变化,如URL变化或特定元素出现
// 由于具体实现不同,这里仅提供日志
}

// 2. 尝试点击提交按钮
const submitButton = await waitForSubmitButton(SUBMIT_WAIT_MAX_ATTEMPTS, SUBMIT_WAIT_DELAY);
if (submitButton) {
submitButton.click();
console.log("✅ 自动提交已通过点击提交按钮触发。");
return true;
} else {
console.warn("⚠️ 未找到提交按钮,尝试其他提交方式。");
}

// 3. 尝试调用JavaScript提交函数
// 需要知道具体的提交函数名称,这里假设为 `submitForm`
// 根据实际情况调整函数名称
try {
if (typeof submitForm === 'function') {
submitForm();
console.log("✅ 自动提交已通过调用JavaScript函数触发。");
return true;
} else {
console.warn("⚠️ 未找到名为 'submitForm' 的提交函数。");
}
} catch (error) {
console.error("调用JavaScript提交函数失败:", error);
}

// 4. 确保事件监听器触发
// 重新触发 'submit' 事件
try {
const form = document.querySelector('form');
if (form) {
const submitEvent = new Event('submit', {
bubbles: true,
cancelable: true
});
form.dispatchEvent(submitEvent);
console.log("✅ 自动提交已通过触发 'submit' 事件触发。");
return true;
} else {
console.warn("⚠️ 未找到表单元素,无法触发 'submit' 事件。");
}
} catch (error) {
console.error("触发 'submit' 事件失败:", error);
}

console.warn("⚠️ 所有自动提交方式均未成功。");
return false;
} finally {
isSubmitting = false;
}
};

const createCustomButtonElement = (name, config) => {
const button = document.createElement('button');
button.innerText = name;
button.type = 'button';
button.style.backgroundColor = config.color;
button.style.color = config.textColor || '#333333';
button.style.border = '1px solid rgba(0,0,0,0.1)';
button.style.borderRadius = '4px';
button.style.padding = '6px 12px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.transition = 'all 0.2s ease';
button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.05)';
button.style.marginBottom = '6px';
button.style.width = 'fit-content';
button.style.textAlign = 'left';
button.style.display = 'block';
// 鼠标悬停显示按钮模板文本
button.title = config.text || '';
return button;
};

// 引入全局变量来跟踪当前打开的文件夹
const currentlyOpenFolder = {
name: null,
element: null
};

const createCustomButton = (name, config, folderName) => {
const button = createCustomButtonElement(name, config, folderName);
button.setAttribute('draggable', 'true');
button.setAttribute('data-button-name', name);
button.setAttribute('data-folder-name', folderName);

button.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('application/json', JSON.stringify({
buttonName: name,
sourceFolder: folderName,
config: config
}));
e.currentTarget.style.opacity = '0.5';
});

button.addEventListener('dragend', (e) => {
e.currentTarget.style.opacity = '1';
});

button.addEventListener('mousedown', (e) => {
e.preventDefault();
const focusedElement = document.activeElement;
if (focusedElement && (focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
setTimeout(() => focusedElement.focus(), 0);
}
});

button.addEventListener('mouseenter', () => {
button.style.transform = 'scale(1.05)';
button.style.boxShadow = '0 3px 6px rgba(0,0,0,0.1)';
});

button.addEventListener('mouseleave', () => {
button.style.transform = 'scale(1)';
button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.05)';
});

// 处理按钮点击事件
button.addEventListener('click', async (e) => {
e.preventDefault();
if (config.type === "template") {
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}

let clipboardText = '';
try {
clipboardText = await navigator.clipboard.readText();
} catch (err) {
console.error("无法访问剪贴板内容:", err);
alert("无法访问剪贴板内容。请检查浏览器权限。");
return;
}

// 改进的内容获取方式
let inputBoxText = '';
if (focusedElement.tagName.toLowerCase() === 'textarea') {
inputBoxText = focusedElement.value;
} else {
// 遍历 contenteditable 元素的子节点,正确处理换行
const childNodes = Array.from(focusedElement.childNodes);
const textParts = [];
let lastWasBr = false;
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.trim() === '') {
if (!lastWasBr && index > 0) {
textParts.push('\n');
}
} else {
textParts.push(node.textContent);
lastWasBr = false;
}
} else if (node.nodeName === 'BR') {
textParts.push('\n');
lastWasBr = true;
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (node.textContent.trim() === '') {
textParts.push('\n');
} else {
if (textParts.length > 0) {
textParts.push('\n');
}
textParts.push(node.textContent);
}
lastWasBr = false;
}
});
inputBoxText = textParts.join('');
}

const selectionText = window.getSelection().toString();

// 新的变量处理逻辑
let finalText = config.text;
// 存储要替换的内容
const replacements = new Map();
const variableSymbol = Symbol('variable');

// 首先标记所有需要替换的模板变量位置
let counter = 0;
finalText = finalText.replace(/\{\{inputboard\}\|\{clipboard\}}/g, (match) => {
const placeholder = `__TEMPLATE_VAR_${counter++}__`;
replacements.set(placeholder, {
type: variableSymbol,
value: inputBoxText.trim() || clipboardText
});
return placeholder;
});
// 处理单个变量
const singleVarReplacements = {
'{clipboard}': clipboardText,
'{inputboard}': inputBoxText,
'{selection}': selectionText
};
// 替换单个变量
Object.entries(singleVarReplacements).forEach(([pattern, value]) => {
const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\__TEMPLATE_VAR_0__');
finalText = finalText.replace(new RegExp(escapedPattern, 'g'), (match) => {
const placeholder = `__TEMPLATE_VAR_${counter++}__`;
replacements.set(placeholder, {
type: variableSymbol,
value: value
});
return placeholder;
});
});
// 最后进行实际替换
replacements.forEach((data, placeholder) => {
if (data.type === variableSymbol) {
finalText = finalText.replace(new RegExp(placeholder, 'g'), data.value);
}
});
// 统一换行符
finalText = finalText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
const containsInputboard = config.text.includes("{inputboard}") ||
config.text.includes("{{inputboard}|{clipboard}}");
if (containsInputboard) {
insertTextSmart(focusedElement, finalText, true);
console.log(`✅ 使用 {inputboard} 变量,输入框内容已被替换。`);
} else {
insertTextSmart(focusedElement, finalText, false);
console.log(`✅ 插入了预设文本。`);
}

// === [修改开始] ===
// 若开启autoSubmit,则先检测是否完成替换,再延时后提交
if (config.autoSubmit) {
try {
// 1) 等待输入框内容与 finalText 匹配,最多等待3秒
await waitForContentMatch(focusedElement, finalText, 100, 3000);

// 2) 再额外等待500ms,确保渲染/加载稳定
await new Promise(resolve => setTimeout(resolve, 500));

// 3) 调用自动提交
const success = await submitForm();
if (success) {
console.log("✅ 自动提交成功(已确认内容替换完成)。");
} else {
console.warn("⚠️ 自动提交失败。");
}
} catch (error) {
console.error("自动提交前检测文本匹配超时或错误:", error);
}
}
// === [修改结束] ===

// 立即关闭弹窗
if (currentlyOpenFolder.name === folderName && currentlyOpenFolder.element) {
currentlyOpenFolder.element.style.display = 'none';
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`✅ 弹窗 "${folderName}" 已立即关闭。`);
} else {
console.warn(`⚠️ 弹窗 "${folderName}" 未被识别为当前打开的弹窗。`);
}

// 若 autoSubmit 为 true,则在此延迟 500ms 再提交
if (config.autoSubmit) {
// ===== [修改开始] =====
// 在这里增加一个延迟等待(500ms),确保文本替换已完成,再进行提交
await new Promise(resolve => setTimeout(resolve, 500));
console.log("⏳ 已等待 500ms,准备自动提交。");
const success = await submitForm();
if (success) {
console.log("✅ 自动提交成功 (在替换后延迟 500ms执行)。");
} else {
console.warn("⚠️ 自动提交失败。");
}
// ===== [修改结束] =====
}

} else if (config.type === "tool") {
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
switch (config.action) {
case "cut":
handleCut(focusedElement);
break;
case "copy":
handleCopy(focusedElement);
break;
case "paste":
handlePaste(focusedElement);
break;
case "clear":
handleClear(focusedElement);
break;
default:
console.warn(`未知的工具按钮动作: ${config.action}`);
}

// 立即关闭弹窗
if (currentlyOpenFolder.name === folderName && currentlyOpenFolder.element) {
currentlyOpenFolder.element.style.display = 'none';
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`✅ 弹窗 "${folderName}" 已立即关闭。`);
} else {
console.warn(`⚠️ 弹窗 "${folderName}" 未被识别为当前打开的弹窗。`);
}
}
});

return button;
};

// 工具按钮动作处理
const handleCut = (element) => {
let text = '';
if (element.tagName.toLowerCase() === 'textarea') {
text = element.value;
insertTextSmart(element, '', true);
} else {
const textContent = [];
const childNodes = Array.from(element.childNodes);
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
textContent.push(node.textContent);
} else if (node.nodeName === 'BR') {
textContent.push('\n');
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (index > -1) textContent.push('\n');
textContent.push(node.textContent);
}
});
text = textContent.join('');
insertTextSmart(element, '', true);
}
if (text) {
navigator.clipboard.writeText(text).then(() => {
console.log("✅ 已剪切输入框内容到剪贴板。");
showTemporaryFeedback(element, '剪切成功');
}).catch(err => {
console.error("剪切失败:", err);
alert("剪切失败,请检查浏览器权限。");
});
}
};

const handleCopy = (element) => {
let text = '';
if (element.tagName.toLowerCase() === 'textarea') {
text = element.value;
} else {
const textContent = [];
const childNodes = Array.from(element.childNodes);
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
textContent.push(node.textContent);
} else if (node.nodeName === 'BR') {
textContent.push('\n');
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (index > -1) textContent.push('\n');
textContent.push(node.textContent);
}
});
text = textContent.join('');
}
if (text) {
navigator.clipboard.writeText(text).then(() => {
console.log("✅ 已复制输入框内容到剪贴板。");
showTemporaryFeedback(element, '复制成功');
}).catch(err => {
console.error("复制失败:", err);
alert("复制失败,请检查浏览器权限。");
});
}
};

const handlePaste = async (element) => {
try {
const clipboardText = await navigator.clipboard.readText();
insertTextSmart(element, clipboardText);
console.log("✅ 已粘贴剪贴板内容到输入框。");
showTemporaryFeedback(element, '粘贴成功');
} catch (err) {
console.error("粘贴失败:", err);
alert("粘贴失败,请检查浏览器权限。");
}
};

const handleClear = (element) => {
insertTextSmart(element, '', true);
console.log("✅ 输入框内容已清空。");
showTemporaryFeedback(element, '清空成功');
};

const showTemporaryFeedback = (element, message) => {
const feedback = document.createElement('span');
feedback.textContent = message;
feedback.style.position = 'absolute';
feedback.style.bottom = '10px';
feedback.style.right = '10px';
feedback.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
feedback.style.color = '#fff';
feedback.style.padding = '4px 8px';
feedback.style.borderRadius = '4px';
feedback.style.zIndex = '10001';
element.parentElement.appendChild(feedback);
setTimeout(() => {
feedback.remove();
}, 1500);
};

const createFolderButton = (folderName, folderConfig) => {
const folderButton = document.createElement('button');
folderButton.innerText = folderName;
folderButton.type = 'button';
folderButton.style.backgroundColor = folderConfig.color;
folderButton.style.color = folderConfig.textColor || '#ffffff';
folderButton.style.border = 'none';
folderButton.style.borderRadius = '4px';
folderButton.style.padding = '6px 12px';
folderButton.style.cursor = 'pointer';
folderButton.style.fontSize = '14px';
folderButton.style.fontWeight = '500';
folderButton.style.transition = 'all 0.2s ease';
folderButton.style.position = 'relative';
folderButton.style.display = 'flex';
folderButton.style.alignItems = 'center';
folderButton.style.zIndex = '99';
folderButton.classList.add('folder-button');
folderButton.setAttribute('data-folder', folderName);

folderButton.addEventListener('mousedown', (e) => {
e.preventDefault();
});

folderButton.addEventListener('mouseleave', () => {
folderButton.style.transform = 'scale(1)';
folderButton.style.boxShadow = 'none';
});

const buttonListContainer = document.createElement('div');
buttonListContainer.style.position = 'fixed';
buttonListContainer.style.display = 'none';
buttonListContainer.style.flexDirection = 'column';
buttonListContainer.style.backgroundColor = 'var(--folder-bg, rgba(255, 255, 255, 0.8))';
buttonListContainer.style.backdropFilter = 'blur(5px)';
buttonListContainer.style.border = `1px solid var(--border-color, #e5e7eb)`;
buttonListContainer.style.borderRadius = '8px';
buttonListContainer.style.padding = '10px';
buttonListContainer.style.paddingBottom = '2.5px';
buttonListContainer.style.boxShadow = `0 4px 12px var(--shadow-color, rgba(0,0,0,0.1))`;
buttonListContainer.style.zIndex = '100';
buttonListContainer.style.maxHeight = '800px';
buttonListContainer.style.overflowY = 'auto';
buttonListContainer.style.transition = 'all 0.3s ease';
buttonListContainer.classList.add('button-list');
buttonListContainer.setAttribute('data-folder-list', folderName);

Object.entries(folderConfig.buttons).forEach(([name, config]) => {
const customButton = createCustomButton(name, config, folderName);
buttonListContainer.appendChild(customButton);
});

folderButton.addEventListener('click', (e) => {
e.preventDefault();
// Toggle popup visibility
if (currentlyOpenFolder.name === folderName) {
// 如果当前文件夹已经打开,则关闭它
buttonListContainer.style.display = 'none';
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`🔒 弹窗 "${folderName}" 已关闭。`);
} else {
// 关闭其他文件夹的弹窗
if (currentlyOpenFolder.element) {
currentlyOpenFolder.element.style.display = 'none';
console.log(`🔒 弹窗 "${currentlyOpenFolder.name}" 已关闭。`);
}
// 打开当前文件夹的弹窗
buttonListContainer.style.display = 'flex';
currentlyOpenFolder.name = folderName;
currentlyOpenFolder.element = buttonListContainer;
console.log(`🔓 弹窗 "${folderName}" 已打开。`);
// 动态定位弹窗位置
const rect = folderButton.getBoundingClientRect();
buttonListContainer.style.bottom = `40px`;
buttonListContainer.style.left = `${rect.left + window.scrollX - 20}px`;
console.log(`📍 弹窗位置设置为 Bottom: 40px, Left: ${rect.left + window.scrollX - 20}px`);
}
});

document.addEventListener('click', (e) => {
if (!folderButton.contains(e.target) && !buttonListContainer.contains(e.target)) {
// 点击了其他地方,关闭弹窗
if (buttonListContainer.style.display !== 'none') {
buttonListContainer.style.display = 'none';
if (currentlyOpenFolder.name === folderName) {
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`🔒 弹窗 "${folderName}" 已关闭(点击外部区域)。`);
}
}
}
});

// 将buttonListContainer移动到body以避免事件传播干扰
document.body.appendChild(buttonListContainer);
return folderButton;
};

const toggleFolder = (folderName, state) => {
const buttonList = document.querySelector(`.button-list[data-folder-list="${folderName}"]`);
if (!buttonList) {
console.warn(`⚠️ 未找到与文件夹 "${folderName}" 关联的弹窗。`);
return;
}
if (state) {
// 打开当前文件夹的弹窗
buttonList.style.display = 'flex';
currentlyOpenFolder.name = folderName;
currentlyOpenFolder.element = buttonList;
console.log(`🔓 弹窗 "${folderName}" 已打开(toggleFolder)。`);
} else {
// 关闭当前文件夹的弹窗
buttonList.style.display = 'none';
if (currentlyOpenFolder.name === folderName) {
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`🔒 弹窗 "${folderName}" 已关闭(toggleFolder)。`);
}
}
// 关闭其他文件夹的弹窗
const allButtonLists = document.querySelectorAll('.button-list');
allButtonLists.forEach(bl => {
if (bl.getAttribute('data-folder-list') !== folderName) {
bl.style.display = 'none';
const fname = bl.getAttribute('data-folder-list');
if (currentlyOpenFolder.name === fname) {
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`🔒 弹窗 "${fname}" 已关闭(toggleFolder 关闭其他弹窗)。`);
}
}
});
};

const closeExistingOverlay = (overlay) => {
if (overlay && overlay.parentElement) {
overlay.parentElement.removeChild(overlay);
}
};

let currentConfirmOverlay = null;
let currentSettingsOverlay = null;
let currentConfigOverlay = null; // 新增的独立配置设置弹窗

const showDeleteFolderConfirmDialog = (folderName, rerenderFn) => {
if (currentConfirmOverlay) {
closeExistingOverlay(currentConfirmOverlay);
}
const folderConfig = buttonConfig.folders[folderName];
if (!folderConfig) {
alert(`文件夹 "${folderName}" 不存在。`);
return;
}
// 构建文件夹内自定义按钮的垂直预览列表
let buttonsPreviewHTML = '';
Object.entries(folderConfig.buttons).forEach(([btnName, btnCfg]) => {
buttonsPreviewHTML += `
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<button style="
background-color: ${btnCfg.color};
color: ${btnCfg.textColor};
border: 1px solid rgba(0,0,0,0.1);
border-radius: 4px;
padding: 4px 8px;
cursor: default;
font-size: 12px;
box-shadow: none;
margin-right: 8px;
" disabled>${btnName}</button>
<span style="font-size: 12px; color: var(--text-color);">${btnName}</span>
</div>
`;
});

const overlay = document.createElement('div');
overlay.classList.add('confirm-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('confirm-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 400px;
max-width: 90vw;
`;
dialog.innerHTML = `
<h3 style="margin: 0 0 20px 0; font-size: 18px; font-weight: 600; color: var(--danger-color, #ef4444);">
🗑️ 确认删除文件夹 "${folderName}"?
</h3>
<p style="margin: 8px 0; color: var(--text-color, #333333);">❗️ 注意:此操作无法撤销!<br/>(删除文件夹将同时删除其中的所有自定义按钮!)</p>
<div style="margin: 16px 0; border: 1px solid var(--border-color, #e5e7eb); padding: 8px; border-radius:4px;">
<!-- 将文件夹按钮预览和文字标签放在一行 -->
<p style="margin:4px 0; display: flex; align-items: center; gap: 8px; color: var(--text-color, #333333);">
<strong>1️⃣ 文件夹按钮外观:</strong>
<button style="
background-color: ${folderConfig.color};
color: ${folderConfig.textColor};
border: none;
border-radius:4px;
padding:6px 12px;
cursor: default;
font-size:14px;
font-weight:500;
box-shadow: none;
" disabled>${folderName}</button>
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
按钮名称: ${folderName}
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
背景颜色: <span style="display:inline-block;width:16px;height:16px;background:${folderConfig.color};border:1px solid #333;vertical-align:middle;margin-right:4px;"></span>${folderConfig.color}
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
文字颜色: <span style="display:inline-block;width:16px;height:16px;background:${folderConfig.textColor};border:1px solid #333;vertical-align:middle;margin-right:4px;"></span>${folderConfig.textColor}
</p>
<hr style="margin: 8px 0; border: none; border-top: 1px solid var(--border-color, #e5e7eb);">
<p style="margin:4px 0; color: var(--text-color, #333333);"><strong>2️⃣ 文件夹内,全部自定义按钮:</strong></p>
<div style="display: flex; flex-direction: column; gap: 8px;">
${buttonsPreviewHTML}
</div>
</div>
<div style="
display: flex;
justify-content: flex-end;
gap: 12px;
border-top:1px solid var(--border-color, #e5e7eb);
padding-top:16px;
">
<button id="cancelDeleteFolder" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius: 4px;
">取消</button>
<button id="confirmDeleteFolder" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--danger-color, #ef4444);
color: white;
border-radius: 4px;
">删除</button>
</div>
`;
overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfirmOverlay = overlay;

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

dialog.querySelector('#cancelDeleteFolder').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
});

dialog.querySelector('#confirmDeleteFolder').addEventListener('click', () => {
delete buttonConfig.folders[folderName];
const idx = buttonConfig.folderOrder.indexOf(folderName);
if (idx > -1) buttonConfig.folderOrder.splice(idx, 1);
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
if (rerenderFn) rerenderFn();
console.log(`🗑️ 文件夹 "${folderName}" 已删除。`);
// 更新按钮栏
updateButtonContainer();
});
};

// 修改 删除按钮确认对话框,增加显示按钮名称、颜色信息及样式预览
const showDeleteButtonConfirmDialog = (folderName, btnName, rerenderFn) => {
if (currentConfirmOverlay) {
closeExistingOverlay(currentConfirmOverlay);
}
const btnCfg = buttonConfig.folders[folderName].buttons[btnName];
if (!btnCfg) {
alert(`按钮 "${btnName}" 不存在于文件夹 "${folderName}" 中。`);
return;
}
const overlay = document.createElement('div');
overlay.classList.add('confirm-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('confirm-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 400px;
max-width: 90vw;
`;
dialog.innerHTML = `
<h3 style="margin: 0 0 20px 0; font-size: 18px; font-weight: 600; color: var(--danger-color, #ef4444);">
🗑️ 确认删除按钮 "${btnName}"?
</h3>
<p style="margin: 8px 0; color: var(--text-color, #333333);">❗️ 注意:此操作无法撤销!</p>
<div style="margin: 16px 0; border: 1px solid var(--border-color, #e5e7eb); padding: 8px; border-radius:4px;">
<p style="margin:4px 0; display: flex; align-items: center; gap: 8px; color: var(--text-color, #333333);">
<strong>1️⃣ 自定义按钮外观:</strong>
<button style="
background-color: ${btnCfg.color};
color: ${btnCfg.textColor};
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: default;
font-size: 12px;
box-shadow: none;
" disabled>${btnName}</button>
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
按钮名称: ${btnName}
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
按钮背景颜色: <span style="display:inline-block;width:16px;height:16px;background:${btnCfg.color};border:1px solid #333;vertical-align:middle;margin-right:4px;"></span>${btnCfg.color}
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
按钮文字颜色: <span style="display:inline-block;width:16px;height:16px;background:${btnCfg.textColor};border:1px solid #333;vertical-align:middle;margin-right:4px;"></span>${btnCfg.textColor}
</p>
<hr style="margin: 8px 0; border: none; border-top: 1px solid var(--border-color, #e5e7eb);">
<p style="margin:4px 0; color: var(--text-color, #333333);"><strong>2️⃣ 按钮对应的文本模板:</strong></p>
<textarea readonly style="
width:100%;
height:150px;
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
border:1px solid var(--border-color, #e5e7eb);
border-radius:4px;
resize: vertical;
">${btnCfg.text || ''}</textarea>
</div>
<div style="
display:flex;
justify-content: flex-end;
gap: 12px;
border-top:1px solid var(--border-color, #e5e7eb);
padding-top:16px;
">
<button id="cancelDeleteButton" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius:4px;
">取消</button>
<button id="confirmDeleteButton" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--danger-color, #ef4444);
color: white;
border-radius:4px;
">删除</button>
</div>
`;
overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfirmOverlay = overlay;

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

dialog.querySelector('#cancelDeleteButton').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
});

dialog.querySelector('#confirmDeleteButton').addEventListener('click', () => {
delete buttonConfig.folders[folderName].buttons[btnName];
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
if (rerenderFn) rerenderFn();
console.log(`🗑️ 按钮 "${btnName}" 已删除。`);
// 更新按钮栏
updateButtonContainer();
});
};

const showButtonEditDialog = (folderName, btnName = '', btnConfig = {}, rerenderFn) => {
if (currentConfirmOverlay) {
closeExistingOverlay(currentConfirmOverlay);
}
// 禁止编辑/删除工具文件夹中的工具按钮
if (folderName === "🖱️" && btnConfig.type === "tool") {
alert('工具文件夹中的工具按钮无法编辑或删除。');
return;
}
const isEdit = btnName !== '';

// Create overlay and dialog containers
const overlay = document.createElement('div');
overlay.classList.add('edit-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('edit-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 500px;
max-width: 90vw;
`;

const initialName = btnName || '';
const initialColor = btnConfig.color || '#FFC1CC';
const initialTextColor = btnConfig.textColor || '#333333';
const initialAutoSubmit = btnConfig.autoSubmit || false; // 新增字段

// 预览部分
const previewSection = `
<div style="
margin: -24px -24px 20px -24px;
padding: 16px 24px;
background-color: var(--button-bg, #f3f4f6);
border-bottom: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px 4px 0 0;
display: flex;
align-items: center;
gap: 16px;
">
<div style="
font-size: 18px;
font-weight: 600;
color: var(--text-color, #333333);
display: flex;
align-items: center;
gap: 8px;
">
${isEdit ? '✏️ 编辑按钮:' : '🆕 新建按钮:'}
</div>
<div id="buttonPreview" style="
display: inline-flex;
padding: 4px;
border-radius: 4px;
background-color: var(--dialog-bg, #ffffff);
">
<button id="previewButton" style="
background-color: ${initialColor};
color: ${initialTextColor};
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: default;
font-size: 14px;
transition: all 0.2s ease;
">${initialName || '预览按钮'}</button>
</div>
</div>
`;

// Tab content for text template
const textTemplateTab = `
<div id="textTemplateTab" class="tab-content" style="display: block;">
<div style="
width: 100%;
padding: 12px;
border-radius: 4px;
border: 1px solid var(--border-color, #e5e7eb);
background-color: var(--button-bg, #f3f4f6);
">
<div style="
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
">
<label style="
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
white-space: nowrap;
">插入变量:</label>
<div id="quickInsertButtons" style="
display: flex;
gap: 8px;
flex-wrap: wrap;
">
<button type="button" data-insert="{inputBoard}" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px;
font-size: 12px;
padding: 4px 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
">📝 输入框</button>
<button type="button" data-insert="{clipboard}" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px;
font-size: 12px;
padding: 4px 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
">📋 剪贴板</button>
<button type="button" data-insert="{selection}" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px;
font-size: 12px;
padding: 4px 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
">🔍 选中</button>
<button type="button" data-insert="{{inputBoard}|{clipboard}}" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px;
font-size: 12px;
padding: 4px 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
">🔄 输入框/剪贴板</button>
</div>
</div>
<textarea id="buttonText" style="
width: 100%;
height: 150px;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color, #e5e7eb);
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
resize: vertical;
">${btnConfig.text || ''}</textarea>
</div>
</div>`;

// Tab content for style settings
const styleSettingsTab = `
<div id="styleSettingsTab" class="tab-content" style="display: none;">
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: var(--text-color, #333333);">按钮名称:</label>
<input type="text" id="buttonName" value="${btnName}" style="
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color, #e5e7eb);
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
">
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: var(--text-color, #333333);">按钮背景颜色:</label>
<input type="color" id="buttonColor" value="${btnConfig.color || '#FFC1CC'}" style="
width: 100px;
height: 40px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
cursor: pointer;
background-color: var(--button-bg, #f3f4f6);
">
</div>
<div style="margin-bottom: 0px;">
<label style="display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: var(--text-color, #333333);">按钮文字颜色:</label>
<input type="color" id="buttonTextColor" value="${btnConfig.textColor || '#333333'}" style="
width: 100px;
height: 40px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
cursor: pointer;
background-color: var(--button-bg, #f3f4f6);
">
</div>
</div>
`;

// 新增的提交设置子标签页
const submitSettingsTab = `
<div id="submitSettingsTab" class="tab-content" style="display: none;">
<div style="margin-bottom: 20px;">
<label style="
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
cursor: pointer;
gap: 6px;
">
<input type="checkbox" id="autoSubmitCheckbox" style="cursor: pointer;" ${initialAutoSubmit ? 'checked' : ''}>
自动提交 (在填充后自动提交内容)
</label>
</div>
</div>
`;

// Tab navigation
const tabNavigation = `
<div style="
display: flex;
gap: 10px;
margin-bottom: 20px;
border-bottom: 1px solid var(--border-color, #e5e7eb);
">
<button class="tab-button active" data-tab="textTemplateTab" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px 4px 0 0;
border-bottom: 2px solid transparent;
">文本模板</button>
<button class="tab-button" data-tab="styleSettingsTab" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
border-radius: 4px 4px 0 0;
border-bottom: 2px solid transparent;
">样式设置</button>
<button class="tab-button" data-tab="submitSettingsTab" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
border-radius: 4px 4px 0 0;
border-bottom: 2px solid transparent;
">提交设置</button>
</div>
`;

// Footer buttons
const footerButtons = `
<div style="
display: flex;
justify-content: space-between;
margin-top: 30px;
padding-top: 20px;
border-top:1px solid var(--border-color, #e5e7eb);
">
<button id="cancelButtonEdit" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius: 4px;
">取消</button>
<button id="saveButtonEdit" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--success-color, #22c55e);
color: white;
border-radius: 4px;
">确认</button>
</div>
`;

// Combine all sections
dialog.innerHTML = `
${previewSection}
${tabNavigation}
${textTemplateTab}
${styleSettingsTab}
${submitSettingsTab}
${footerButtons}
`;

// Add tab switching functionality
const setupTabs = () => {
const tabButtons = dialog.querySelectorAll('.tab-button');
const tabContents = dialog.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const tabId = button.dataset.tab;
// Update button styles
tabButtons.forEach(btn => {
if (btn === button) {
btn.style.backgroundColor = 'var(--primary-color, #3B82F6)';
btn.style.color = 'white';
btn.style.borderBottom = '2px solid var(--primary-color, #3B82F6)';
} else {
btn.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
btn.style.color = 'var(--text-color, #333333)';
btn.style.borderBottom = '2px solid transparent';
}
});
// Show/hide content
tabContents.forEach(content => {
content.style.display = content.id === tabId ? 'block' : 'none';
});
});
});
};

// Rest of the existing dialog setup code...
overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfirmOverlay = overlay;

// Setup tabs
setupTabs();

// Setup preview updates
const setupPreviewUpdates = () => {
const previewButton = dialog.querySelector('#previewButton');
const buttonNameInput = dialog.querySelector('#buttonName');
const buttonColorInput = dialog.querySelector('#buttonColor');
const buttonTextColorInput = dialog.querySelector('#buttonTextColor');
const autoSubmitCheckbox = dialog.querySelector('#autoSubmitCheckbox'); // 新增引用

buttonNameInput?.addEventListener('input', (e) => {
previewButton.textContent = e.target.value || '预览按钮';
});

buttonColorInput?.addEventListener('input', (e) => {
previewButton.style.backgroundColor = e.target.value;
});

buttonTextColorInput?.addEventListener('input', (e) => {
previewButton.style.color = e.target.value;
});

// 监听“自动提交”开关变化
autoSubmitCheckbox?.addEventListener('change', (e) => {
console.log(`✅ 自动提交开关已设置为 ${e.target.checked}`);
});
};
setupPreviewUpdates();

// Setup quick insert buttons
const setupQuickInsert = () => {
const buttonText = dialog.querySelector('#buttonText');
const quickInsertButtons = dialog.querySelector('#quickInsertButtons');
quickInsertButtons?.addEventListener('click', (e) => {
const button = e.target.closest('button[data-insert]');
if (!button) return;
e.preventDefault();
const insertText = button.dataset.insert;
const start = buttonText.selectionStart;
const end = buttonText.selectionEnd;
buttonText.value = buttonText.value.substring(0, start) +
insertText +
buttonText.value.substring(end);
buttonText.selectionStart = buttonText.selectionEnd = start + insertText.length;
buttonText.focus();
});
quickInsertButtons?.addEventListener('mousedown', (e) => {
if (e.target.closest('button[data-insert]')) {
e.preventDefault();
}
});
};
setupQuickInsert();

// Animation effect
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

// Setup buttons
dialog.querySelector('#cancelButtonEdit')?.addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
});

dialog.querySelector('#saveButtonEdit')?.addEventListener('click', () => {
const newBtnName = dialog.querySelector('#buttonName').value.trim();
const newBtnColor = dialog.querySelector('#buttonColor').value;
const newBtnTextColor = dialog.querySelector('#buttonTextColor').value;
const newBtnText = dialog.querySelector('#buttonText').value.trim();
const autoSubmit = dialog.querySelector('#autoSubmitCheckbox')?.checked || false; // 获取自动提交状态

if (!newBtnName) {
alert('请输入按钮名称!');
return;
}

if (!isValidColor(newBtnColor) || !isValidColor(newBtnTextColor)) {
alert('请选择有效的颜色!');
return;
}

if (newBtnName !== btnName && buttonConfig.folders[folderName].buttons[newBtnName]) {
alert('按钮名称已存在!');
return;
}

// Get all buttons order
const currentButtons = { ...buttonConfig.folders[folderName].buttons };

if (btnConfig.type === "tool") {
// 工具按钮不允许更改类型和动作
buttonConfig.folders[folderName].buttons[newBtnName] = {
type: "tool",
action: btnConfig.action,
color: newBtnColor,
textColor: newBtnTextColor
};
} else {
// 处理模板按钮
// Handle button rename
if (btnName && newBtnName !== btnName) {
const newButtons = {};
Object.keys(currentButtons).forEach(key => {
if (key === btnName) {
newButtons[newBtnName] = {
text: newBtnText,
color: newBtnColor,
textColor: newBtnTextColor,
type: "template",
autoSubmit: autoSubmit
};
} else {
newButtons[key] = currentButtons[key];
}
});
buttonConfig.folders[folderName].buttons = newButtons;
} else {
// Update existing button
if (btnName) {
buttonConfig.folders[folderName].buttons[btnName] = {
text: newBtnText,
color: newBtnColor,
textColor: newBtnTextColor,
type: "template",
autoSubmit: autoSubmit
};
} else {
// Create new button
buttonConfig.folders[folderName].buttons[newBtnName] = {
text: newBtnText,
color: newBtnColor,
textColor: newBtnTextColor,
type: "template",
autoSubmit: autoSubmit
};
}
}
}

localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
if (rerenderFn) rerenderFn();
console.log(`✅ 按钮 "${newBtnName}" 已保存。`);
updateButtonContainer();
});
};

function isValidColor(color) {
const s = new Option().style;
s.color = color;
return s.color !== '';
}

const showFolderEditDialog = (folderName = '', folderConfig = {}, rerenderFn) => {
if (currentConfirmOverlay) {
closeExistingOverlay(currentConfirmOverlay);
}
const isNew = !folderName;
const overlay = document.createElement('div');
overlay.classList.add('folder-edit-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('folder-edit-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 500px;
max-width: 90vw;
`;

const initialName = folderName || '';
const initialColor = folderConfig.color || '#3B82F6';
const initialTextColor = folderConfig.textColor || '#ffffff';

// 预览部分
const previewSection = `
<div style="
margin: -24px -24px 20px -24px;
padding: 16px 24px;
background-color: var(--button-bg, #f3f4f6);
border-bottom: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px 4px 0 0;
display: flex;
align-items: center;
gap: 16px;
">
<div style="
font-size: 18px;
font-weight: 600;
color: var(--text-color, #333333);
display: flex;
align-items: center;
gap: 8px;
">
${isNew ? '🆕 新建文件夹:' : '✏️ 编辑文件夹:'}
</div>
<div id="folderPreview" style="
display: inline-flex;
padding: 4px;
border-radius: 4px;
background-color: var(--dialog-bg, #ffffff);
">
<button id="previewButton" style="
background-color: ${initialColor};
color: ${initialTextColor};
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: default;
font-size: 14px;
transition: all 0.2s ease;
">${initialName || '预览文件夹'}</button>
</div>
</div>
`;

// 设置部分
const settingsSection = `
<div style="
display:flex;
flex-direction:column;
gap:20px;
margin-bottom:20px;
">
<div style="margin-bottom: 20px;">
<label style="
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
">文件夹名称:</label>
<input type="text" id="folderNameInput" value="${initialName}" style="
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color, #e5e7eb);
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
">
</div>
<div style="margin-bottom: 20px;">
<label style="
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
">按钮背景颜色:</label>
<input type="color" id="folderColorInput" value="${initialColor}" style="
width: 100px;
height: 40px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
cursor: pointer;
background-color: var(--button-bg, #f3f4f6);
">
</div>
<div style="margin-bottom: 0px;">
<label style="
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
">按钮文字颜色:</label>
<input type="color" id="folderTextColorInput" value="${initialTextColor}" style="
width: 100px;
height: 40px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
cursor: pointer;
background-color: var(--button-bg, #f3f4f6);
">
</div>
</div>
`;

// 底部按钮
const footerButtons = `
<div style="
display: flex;
justify-content: space-between;
margin-top: 30px;
padding-top: 20px;
border-top:1px solid var(--border-color, #e5e7eb);
">
<button id="cancelFolderEdit" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius: 4px;
">取消</button>
<button id="saveFolderEdit" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--success-color, #22c55e);
color: white;
border-radius: 4px;
">确认</button>
</div>
`;

// Combine all sections
dialog.innerHTML = `
${previewSection}
${settingsSection}
${footerButtons}
`;

// 添加事件监听器
const setupPreviewUpdates = () => {
const previewButton = dialog.querySelector('#previewButton');
const folderNameInput = dialog.querySelector('#folderNameInput');
const folderColorInput = dialog.querySelector('#folderColorInput');
const folderTextColorInput = dialog.querySelector('#folderTextColorInput');

folderNameInput?.addEventListener('input', (e) => {
previewButton.textContent = e.target.value || '预览文件夹';
});

folderColorInput?.addEventListener('input', (e) => {
previewButton.style.backgroundColor = e.target.value;
});

folderTextColorInput?.addEventListener('input', (e) => {
previewButton.style.color = e.target.value;
});
};
setupPreviewUpdates();

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfirmOverlay = overlay;

// Animation effect
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

// Setup buttons
dialog.querySelector('#cancelFolderEdit').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
});

dialog.querySelector('#saveFolderEdit').addEventListener('click', () => {
const newFolderName = dialog.querySelector('#folderNameInput').value.trim();
const newColor = dialog.querySelector('#folderColorInput').value;
const newTextColor = dialog.querySelector('#folderTextColorInput').value;

if (!newFolderName) {
alert('请输入文件夹名称');
return;
}

if (isNew && buttonConfig.folders[newFolderName]) {
alert("该文件夹已存在!");
return;
}

if (!isNew && newFolderName !== folderName && buttonConfig.folders[newFolderName]) {
alert("该文件夹已存在!");
return;
}

if (!isNew && newFolderName !== folderName) {
const oldButtons = buttonConfig.folders[folderName].buttons;
buttonConfig.folders[newFolderName] = {
...buttonConfig.folders[folderName],
color: newColor,
textColor: newTextColor,
buttons: { ...oldButtons }
};
delete buttonConfig.folders[folderName];
const idx = buttonConfig.folderOrder.indexOf(folderName);
if (idx > -1) {
buttonConfig.folderOrder[idx] = newFolderName;
}
} else {
buttonConfig.folders[newFolderName] = buttonConfig.folders[newFolderName] || { buttons: {} };
buttonConfig.folders[newFolderName].color = newColor;
buttonConfig.folders[newFolderName].textColor = newTextColor;
}

// 确保所有按钮都有'type'字段和'autoSubmit'字段
Object.entries(buttonConfig.folders).forEach(([folderName, folderCfg]) => {
Object.entries(folderCfg.buttons).forEach(([btnName, btnCfg]) => {
if (!btnCfg.type) {
if (folderName === "🖱️") {
btnCfg.type = "tool";
} else {
btnCfg.type = "template";
}
}
if (btnCfg.type === "template" && typeof btnCfg.autoSubmit !== 'boolean') {
btnCfg.autoSubmit = false;
}
});
});

localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
if (rerenderFn) rerenderFn(newFolderName);
console.log(`✅ 文件夹 "${newFolderName}" 已保存。`);
updateButtonContainer();
});
};

const createSettingsButton = () => {
const button = document.createElement('button');
button.innerText = '⚙️';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.addEventListener('click', showUnifiedSettingsDialog);
return button;
};

const createCutButton = () => {
const button = document.createElement('button');
button.innerText = '✂️';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.title = '剪切输入框内容';
// 阻止mousedown默认行为以维持输入框焦点
button.addEventListener('mousedown', (e) => {
e.preventDefault();
});
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
let text = '';
if (focusedElement.tagName.toLowerCase() === 'textarea') {
text = focusedElement.value;
// 清空textarea内容
const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
nativeSetter.call(focusedElement, '');
const inputEvent = new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'deleteContent'
});
focusedElement.dispatchEvent(inputEvent);
} else {
// 处理contenteditable元素
const childNodes = Array.from(focusedElement.childNodes);
const textParts = [];
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
textParts.push(node.textContent);
} else if (node.nodeName === 'BR') {
textParts.push('\n');
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (index > -1) textParts.push('\n');
textParts.push(node.textContent);
}
});
text = textParts.join('');
// 清空contenteditable内容
focusedElement.innerHTML = '';
}
if (text) {
navigator.clipboard.writeText(text).then(() => {
console.log("✅ 已剪切输入框内容到剪贴板。");
showTemporaryFeedback(focusedElement, '剪切成功');
}).catch(err => {
console.error("剪切失败:", err);
alert("剪切失败,请检查浏览器权限。");
});
}
// 确保输入框保持焦点
focusedElement.focus();
// 如果是textarea,还需要设置光标位置到开始处
if (focusedElement.tagName.toLowerCase() === 'textarea') {
focusedElement.selectionStart = focusedElement.selectionEnd = 0;
}
console.log("✅ 输入框内容已清空。");
showTemporaryFeedback(focusedElement, '清空成功');
});
return button;
};

const createCopyButton = () => {
const button = document.createElement('button');
button.innerText = '🅲';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.title = '复制输入框内容';
// 阻止mousedown默认行为以维持输入框焦点
button.addEventListener('mousedown', (e) => {
e.preventDefault();
});
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
let text = '';
if (focusedElement.tagName.toLowerCase() === 'textarea') {
text = focusedElement.value;
} else {
const textContent = [];
const childNodes = Array.from(focusedElement.childNodes);
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
textContent.push(node.textContent);
} else if (node.nodeName === 'BR') {
textContent.push('\n');
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (index > -1) textContent.push('\n');
textContent.push(node.textContent);
}
});
text = textContent.join('');
}
if (text) {
navigator.clipboard.writeText(text).then(() => {
console.log("✅ 已复制输入框内容到剪贴板。");
showTemporaryFeedback(focusedElement, '复制成功');
}).catch(err => {
console.error("复制失败:", err);
alert("复制失败,请检查浏览器权限。");
});
}
// 确保输入框保持焦点
focusedElement.focus();
});
return button;
};

const createPasteButton = () => {
const button = document.createElement('button');
button.innerText = '🆅';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.title = '粘贴剪切板内容';
// 阻止mousedown默认行为以维持输入框焦点
button.addEventListener('mousedown', (e) => {
e.preventDefault();
});
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
try {
const clipboardText = await navigator.clipboard.readText();
// 使用现有的insertTextSmart函数插入文本
insertTextSmart(focusedElement, clipboardText);
// 添加视觉反馈
const originalText = button.innerText;
button.innerText = '✓';
button.style.backgroundColor = 'var(--success-color, #22c55e)';
button.style.color = 'white';
setTimeout(() => {
button.innerText = originalText;
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
}, 1000);
console.log("✅ 已粘贴剪贴板内容到输入框。");
} catch (err) {
console.error("访问剪切板失败:", err);
alert("粘贴失败,请检查浏览器权限。");
}
// 确保输入框保持焦点
focusedElement.focus();
});
return button;
};

const createClearButton = () => {
const button = document.createElement('button');
button.innerText = '✖️';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.title = '清空输入框';
// 添加mousedown事件处理器来阻止焦点切换
button.addEventListener('mousedown', (e) => {
e.preventDefault();
});
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation(); // 阻止事件冒泡
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
// 使用现有的insertTextSmart函数清空内容
insertTextSmart(focusedElement, '', true);
// 确保立即重新聚焦
focusedElement.focus();
// 如果是textarea,还需要设置光标位置到开始处
if (focusedElement.tagName.toLowerCase() === 'textarea') {
focusedElement.selectionStart = focusedElement.selectionEnd = 0;
}
console.log("✅ 输入框内容已清空。");
showTemporaryFeedback(focusedElement, '清空成功');
});
return button;
};

// 新增的配置设置按钮和弹窗
const createConfigSettingsButton = () => {
const button = document.createElement('button');
button.innerText = '🛠️ 配置管理';
button.type = 'button';
button.style.backgroundColor = 'var(--info-color, #4F46E5)';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.addEventListener('click', showConfigSettingsDialog);
return button;
};

function exportConfig() {
const date = new Date();
const yyyy = date.getFullYear();
const mm = String(date.getMonth() + 1).padStart(2, '0');
const dd = String(date.getDate()).padStart(2, '0');
const fileName = `ChatGPT Template Text Folders [${yyyy}-${mm}-${dd}].json`;
const dataStr = JSON.stringify(buttonConfig, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
console.log("📤 配置已导出。");
}

function importConfig(rerenderFn) {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,application/json';
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (evt) => {
try {
const importedConfig = JSON.parse(evt.target.result);
if (importedConfig && typeof importedConfig === 'object') {
if (!importedConfig.folders || !importedConfig.folderOrder) {
alert('导入的配置文件无效!缺少必要字段。');
return;
}
// 替换现有配置
buttonConfig = importedConfig;
// 确保所有按钮都有'type'字段和'autoSubmit'字段
Object.entries(buttonConfig.folders).forEach(([folderName, folderConfig]) => {
Object.entries(folderConfig.buttons).forEach(([btnName, btnCfg]) => {
if (!btnCfg.type) {
if (folderName === "🖱️") {
btnCfg.type = "tool";
} else {
btnCfg.type = "template";
}
}
if (btnCfg.type === "template" && typeof btnCfg.autoSubmit !== 'boolean') {
btnCfg.autoSubmit = false;
}
});
});
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
if (rerenderFn) rerenderFn();
console.log("📥 配置已成功导入。");
// 更新按钮栏
updateButtonContainer();
} else {
alert('导入的配置文件内容无效!');
}
} catch (error) {
alert('导入的配置文件解析失败!请确认文件格式正确。');
}
};
reader.readAsText(file);
});
input.click();
}

// 新增的单独配置设置弹窗
const showConfigSettingsDialog = () => {
if (currentConfigOverlay) {
closeExistingOverlay(currentConfigOverlay);
}
const overlay = document.createElement('div');
overlay.classList.add('config-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 12000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('config-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 400px;
max-width: 90vw;
`;

dialog.innerHTML = `
<h3 style="margin:0 0 20px 0;font-size:18px;font-weight:600; color: var(--text-color, #333333);">🛠️ 配置管理</h3>
<div style="
display:flex;
flex-direction:column;
gap:20px;
margin-bottom:20px;
">
<!-- 重置按钮部分 -->
<div style="
display:flex;
flex-direction:row;
align-items:center;
padding-bottom:16px;
border-bottom:1px solid var(--border-color, #e5e7eb);
">
<span style="margin-right:12px;color: var(--text-color, #333333);">恢复默认设置:</span>
<button id="resetSettingsBtn" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius:4px;
">↩️ 重置</button>
</div>
<!-- 导入导出部分 -->
<div style="
display:flex;
flex-direction:row;
align-items:center;
">
<span style="margin-right:12px;color: var(--text-color, #333333);">配置导入导出:</span>
<div style="display:flex;gap:8px;">
<button id="importConfigBtn" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--add-color, #fd7e14);
color: white;
border-radius:4px;
">📥 导入</button>
<button id="exportConfigBtn" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--success-color, #22c55e);
color: white;
border-radius:4px;
">📤 导出</button>
</div>
</div>
</div>
<!-- 底部关闭按钮 -->
<div style="
display:flex;
justify-content:flex-end;
border-top:1px solid var(--border-color, #e5e7eb);
padding-top:16px;
">
<button id="closeConfigDialog" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius:4px;
">关闭</button>
</div>
`;

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfigOverlay = overlay;

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

dialog.querySelector('#importConfigBtn').addEventListener('click', () => {
importConfig(() => {
// 重新渲染主设置面板
if (currentSettingsOverlay) {
selectedFolderName = buttonConfig.folderOrder[0] || null;
renderFolderList();
renderButtonList();
}
});
});

dialog.querySelector('#exportConfigBtn').addEventListener('click', exportConfig);

dialog.querySelector('#resetSettingsBtn').addEventListener('click', () => {
if (confirm('确认重置所有配置为默认设置吗?')) {
buttonConfig = JSON.parse(JSON.stringify(defaultConfig));
// 重置folderOrder
buttonConfig.folderOrder = Object.keys(buttonConfig.folders);
// 确保所有按钮都有'type'字段和'autoSubmit'字段
Object.entries(buttonConfig.folders).forEach(([folderName, folderConfig]) => {
Object.entries(folderConfig.buttons).forEach(([btnName, btnCfg]) => {
if (!btnCfg.type) {
if (folderName === "🖱️") {
btnCfg.type = "tool";
} else {
btnCfg.type = "template";
}
}
if (btnCfg.type === "template" && typeof btnCfg.autoSubmit !== 'boolean') {
btnCfg.autoSubmit = false;
}
});
});
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
if (currentSettingsOverlay) {
selectedFolderName = buttonConfig.folderOrder[0] || null;
renderFolderList();
renderButtonList();
}
alert('已重置为默认配置');
console.log("🔄 配置已重置为默认设置。");
// 更新按钮栏
updateButtonContainer();
}
});

dialog.querySelector('#closeConfigDialog').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfigOverlay = null;
});
};

let selectedFolderName = buttonConfig.folderOrder[0] || null; // 在设置面板中使用
let folderListContainer, buttonListContainer; // 在渲染函数中定义

const renderFolderList = () => {
if (!folderListContainer) return;
folderListContainer.innerHTML = '';
const foldersArray = buttonConfig.folderOrder.map(fname => [fname, buttonConfig.folders[fname]]).filter(([f,c])=>c);
foldersArray.forEach(([fname, fconfig]) => {
const folderItem = document.createElement('div');
folderItem.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-radius: 4px;
margin: 4px 0;
background-color: ${selectedFolderName === fname ? (isDarkMode() ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0,0,0,0.1)') : 'transparent'};
cursor: move;
`;
folderItem.classList.add('folder-item');
folderItem.setAttribute('draggable', 'true');
folderItem.setAttribute('data-folder', fname);

const {container: leftInfo, folderPreview} = (function createFolderPreview(fname, fconfig){
const container = document.createElement('div');
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.gap = '8px';
const folderPreview = document.createElement('button');
folderPreview.textContent = fname;
folderPreview.style.backgroundColor = fconfig.color;
folderPreview.style.color = fconfig.textColor || '#ffffff';
folderPreview.style.border = 'none';
folderPreview.style.borderRadius = '4px';
folderPreview.style.padding = '4px 8px';
folderPreview.style.fontSize = '14px';
folderPreview.style.cursor = 'grab';
container.appendChild(folderPreview);
return {container, folderPreview};
})(fname, fconfig);

const rightBtns = document.createElement('div');
rightBtns.style.display = 'flex';
rightBtns.style.gap = '20px';

// 创建编辑按钮
const editFolderBtn = document.createElement('button');
editFolderBtn.textContent = '✏️';
editFolderBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
font-size: 14px;
color: var(--primary-color, #3B82F6);
`;
editFolderBtn.addEventListener('click', (e) => {
e.stopPropagation();
showFolderEditDialog(fname, fconfig, (newFolderName) => {
selectedFolderName = newFolderName;
renderFolderList();
renderButtonList();
});
});

const deleteFolderBtn = document.createElement('button');
deleteFolderBtn.textContent = '🗑️';
deleteFolderBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
font-size: 14px;
color: var(--danger-color, #ef4444);
`;
deleteFolderBtn.addEventListener('click', (e) => {
e.stopPropagation();
showDeleteFolderConfirmDialog(fname, () => {
const allFolders = buttonConfig.folderOrder;
selectedFolderName = allFolders[0] || null;
renderFolderList();
renderButtonList();
});
});

rightBtns.appendChild(editFolderBtn);
rightBtns.appendChild(deleteFolderBtn);

folderItem.appendChild(leftInfo);
folderItem.appendChild(rightBtns);

folderItem.addEventListener('click', () => {
selectedFolderName = fname;
renderFolderList();
renderButtonList();
});

folderItem.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', fname);
folderItem.style.opacity = '0.5';
});

folderItem.addEventListener('dragover', (e) => {
e.preventDefault();
});

folderItem.addEventListener('dragenter', () => {
folderItem.style.border = `2px solid var(--primary-color, #3B82F6)`;
});

folderItem.addEventListener('dragleave', () => {
folderItem.style.border = 'none';
});

folderItem.addEventListener('drop', (e) => {
e.preventDefault();
const draggedFolder = e.dataTransfer.getData('text/plain');
if (draggedFolder && draggedFolder !== fname) {
const draggedIndex = buttonConfig.folderOrder.indexOf(draggedFolder);
const targetIndex = buttonConfig.folderOrder.indexOf(fname);
if (draggedIndex > -1 && targetIndex > -1) {
const [removed] = buttonConfig.folderOrder.splice(draggedIndex, 1);
buttonConfig.folderOrder.splice(targetIndex, 0, removed);
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
renderFolderList();
renderButtonList();
console.log(`🔄 文件夹顺序已更新:${draggedFolder} 移动到 ${fname} 前。`);
// 更新按钮栏
updateButtonContainer();
}
}
// Check if a button is being dropped onto this folder
const buttonData = e.dataTransfer.getData('application/json');
if (buttonData) {
try {
const { buttonName: draggedBtnName, sourceFolder } = JSON.parse(buttonData);
if (draggedBtnName && sourceFolder && sourceFolder !== fname) {
// Move the button from sourceFolder to fname
const button = buttonConfig.folders[sourceFolder].buttons[draggedBtnName];
if (button) {
// Remove from source
delete buttonConfig.folders[sourceFolder].buttons[draggedBtnName];
// Add to target
buttonConfig.folders[fname].buttons[draggedBtnName] = button;
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
renderFolderList();
renderButtonList();
console.log(`🔄 按钮 "${draggedBtnName}" 已从 "${sourceFolder}" 移动到 "${fname}"。`);
// Update button container
updateButtonContainer();
}
}
} catch (error) {
console.error("解析拖放数据失败:", error);
}
}
folderItem.style.border = 'none';
});

folderItem.addEventListener('dragend', () => {
folderItem.style.opacity = '1';
});

folderListContainer.appendChild(folderItem);
});
};

const renderButtonList = () => {
if (!buttonListContainer) return;
buttonListContainer.innerHTML = '';
if (!selectedFolderName) return;
const currentFolderConfig = buttonConfig.folders[selectedFolderName];
if (!currentFolderConfig) return;

const rightHeader = document.createElement('div');
rightHeader.style.display = 'flex';
rightHeader.style.justifyContent = 'space-between';
rightHeader.style.alignItems = 'center';
rightHeader.style.marginBottom = '8px';

const folderNameLabel = document.createElement('h3');
folderNameLabel.innerHTML = `➤ <strong>${selectedFolderName}</strong>`;
folderNameLabel.style.margin = '0';

const addNewButtonBtn = document.createElement('button');
Object.assign(addNewButtonBtn.style, styles.button, {
backgroundColor: 'var(--add-color, #fd7e14)',
color: 'white',
borderRadius: '4px'
});
addNewButtonBtn.textContent = '+ 新建按钮';
addNewButtonBtn.addEventListener('click', () => {
showButtonEditDialog(selectedFolderName, '', {}, () => {
renderButtonList();
});
});

rightHeader.appendChild(folderNameLabel);
rightHeader.appendChild(addNewButtonBtn);

buttonListContainer.appendChild(rightHeader);

const btnScrollArea = document.createElement('div');
btnScrollArea.style.flex = '1';
btnScrollArea.style.overflowY = 'auto';
btnScrollArea.style.border = `1px solid var(--border-color, #e5e7eb)`;
btnScrollArea.style.borderRadius = '4px';
btnScrollArea.style.padding = '8px';

const currentFolderButtons = Object.entries(currentFolderConfig.buttons);
const createButtonPreview = (btnName, btnCfg) => {
const btnEl = createCustomButtonElement(btnName, btnCfg);
btnEl.style.marginBottom = '0px';
btnEl.style.marginRight = '8px';
btnEl.style.cursor = 'grab';
return btnEl;
};

currentFolderButtons.forEach(([btnName, cfg]) => {
const btnItem = document.createElement('div');
btnItem.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding: 4px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
background-color: var(--button-bg, #f3f4f6);
cursor: move;
`;
btnItem.setAttribute('draggable', 'true');
btnItem.setAttribute('data-button-name', btnName);

const leftPart = document.createElement('div');
leftPart.style.display = 'flex';
leftPart.style.alignItems = 'center';
leftPart.style.gap = '8px';

const btnPreview = createButtonPreview(btnName, cfg);
leftPart.appendChild(btnPreview);

const opsDiv = document.createElement('div');
opsDiv.style.display = 'flex';
opsDiv.style.gap = '20px';

// 创建“自动提交”开关
const autoSubmitLabel = document.createElement('label');
autoSubmitLabel.style.display = 'flex';
autoSubmitLabel.style.alignItems = 'center';
autoSubmitLabel.style.gap = '6px';
autoSubmitLabel.style.cursor = 'pointer';
const autoSubmitCheckbox = document.createElement('input');
autoSubmitCheckbox.type = 'checkbox';
autoSubmitCheckbox.checked = cfg.autoSubmit || false;
autoSubmitCheckbox.style.cursor = 'pointer';
autoSubmitLabel.appendChild(autoSubmitCheckbox);
autoSubmitLabel.appendChild(document.createTextNode('自动提交'));
autoSubmitCheckbox.addEventListener('change', () => {
cfg.autoSubmit = autoSubmitCheckbox.checked;
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
console.log(`✅ 按钮 "${btnName}" 的自动提交已设置为 ${autoSubmitCheckbox.checked}`);
});

// 创建编辑按钮
const editBtn = document.createElement('button');
editBtn.textContent = '✏️';
editBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
color: var(--primary-color, #3B82F6);
font-size: 14px;
`;
editBtn.addEventListener('click', (e) => {
e.stopPropagation();
showButtonEditDialog(selectedFolderName, btnName, cfg, () => {
renderButtonList();
});
});

// 创建删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '🗑️';
deleteBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
color: var(--danger-color, #ef4444);
font-size: 14px;
`;
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
showDeleteButtonConfirmDialog(selectedFolderName, btnName, () => {
renderButtonList();
});
});

opsDiv.appendChild(autoSubmitLabel); // 添加“自动提交”开关
opsDiv.appendChild(editBtn);
opsDiv.appendChild(deleteBtn);

btnItem.appendChild(leftPart);
btnItem.appendChild(opsDiv);

btnItem.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('application/json', JSON.stringify({
buttonName: btnName,
sourceFolder: selectedFolderName
}));
btnItem.style.opacity = '0.5';
});

btnItem.addEventListener('dragover', (e) => {
e.preventDefault();
});

btnItem.addEventListener('dragenter', () => {
btnItem.style.border = `2px solid var(--primary-color, #3B82F6)`;
});

btnItem.addEventListener('dragleave', () => {
btnItem.style.border = `1px solid var(--border-color, #e5e7eb)`;
});

btnItem.addEventListener('drop', (e) => {
e.preventDefault();
const data = JSON.parse(e.dataTransfer.getData('application/json'));
const { buttonName: draggedBtnName } = data;
if (draggedBtnName && draggedBtnName !== btnName) {
const buttonsKeys = Object.keys(buttonConfig.folders[selectedFolderName].buttons);
const draggedIndex = buttonsKeys.indexOf(draggedBtnName);
const targetIndex = buttonsKeys.indexOf(btnName);
if (draggedIndex > -1 && targetIndex > -1) {
const reordered = [...buttonsKeys];
reordered.splice(draggedIndex, 1);
reordered.splice(targetIndex, 0, draggedBtnName);
const newOrderedMap = {};
reordered.forEach(k => {
newOrderedMap[k] = buttonConfig.folders[selectedFolderName].buttons[k];
});
buttonConfig.folders[selectedFolderName].buttons = newOrderedMap;
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
renderButtonList();
console.log(`🔄 按钮顺序已更新:${draggedBtnName} 移动到 ${btnName} 前。`);
// 更新按钮栏
updateButtonContainer();
}
}
btnItem.style.border = `1px solid var(--border-color, #e5e7eb)`;
});

btnItem.addEventListener('dragend', () => {
btnItem.style.opacity = '1';
});

btnScrollArea.appendChild(btnItem);
});

buttonListContainer.appendChild(btnScrollArea);
};

const showUnifiedSettingsDialog = () => {
if (currentSettingsOverlay) {
closeExistingOverlay(currentSettingsOverlay);
}
const overlay = document.createElement('div');
overlay.classList.add('settings-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('settings-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 800px;
max-width: 95vw;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
`;

const header = document.createElement('div');
header.style.display = 'flex';
header.style.justifyContent = 'space-between';
header.style.alignItems = 'center';
header.style.marginBottom = '16px';

const title = document.createElement('h2');
title.textContent = "⚙️ 设置面板";
title.style.margin = '0';
title.style.fontSize = '20px';
title.style.fontWeight = '600';

// ===== [修改开始] =====
// 添加"自动化"按钮,并与原有“配置管理”按钮并排
const headerBtnsWrapper = document.createElement('div');
headerBtnsWrapper.style.display = 'flex';
headerBtnsWrapper.style.gap = '10px';

// 新建自动化按钮
const automationBtn = document.createElement('button');
automationBtn.innerText = '⚡ 自动化';
automationBtn.type = 'button';
automationBtn.style.backgroundColor = 'var(--info-color, #4F46E5)';
automationBtn.style.color = 'white';
automationBtn.style.border = 'none';
automationBtn.style.borderRadius = '4px';
automationBtn.style.padding = '5px 10px';
automationBtn.style.cursor = 'pointer';
automationBtn.style.fontSize = '14px';
automationBtn.addEventListener('click', () => {
showAutomationSettingsDialog();
});
headerBtnsWrapper.appendChild(automationBtn);

// 原有创建配置管理按钮
const openConfigBtn = createConfigSettingsButton();
headerBtnsWrapper.appendChild(openConfigBtn);

// 原有保存关闭按钮
const saveSettingsBtn = document.createElement('button');
Object.assign(saveSettingsBtn.style, styles.button, {
backgroundColor: 'var(--success-color, #22c55e)',
color: 'white',
borderRadius: '4px'
});
saveSettingsBtn.textContent = '💾 关闭并保存';
saveSettingsBtn.addEventListener('click', () => {
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentSettingsOverlay = null;
attachButtons();
console.log("✅ 设置已保存并关闭设置面板。");
updateButtonContainer();
});
headerBtnsWrapper.appendChild(saveSettingsBtn);
// ===== [修改结束] =====

header.appendChild(title);
header.appendChild(headerBtnsWrapper);

const mainContainer = document.createElement('div');
mainContainer.style.display = 'flex';
mainContainer.style.flex = '1';
mainContainer.style.overflow = 'hidden';
mainContainer.style.borderTop = `1px solid var(--border-color, #e5e7eb)`;

const folderPanel = document.createElement('div');
folderPanel.style.display = 'flex';
folderPanel.style.flexDirection = 'column';
folderPanel.style.width = '220px';
folderPanel.style.borderRight = `1px solid var(--border-color, #e5e7eb)`;
folderPanel.style.marginRight = '12px';
folderPanel.style.overflowY = 'auto';
folderPanel.style.padding = '8px';

folderListContainer = document.createElement('div');
folderListContainer.style.flex = '1';
folderListContainer.style.overflowY = 'auto';
folderListContainer.style.padding = '8px';

const folderAddContainer = document.createElement('div');
folderAddContainer.style.padding = '8px';
folderAddContainer.style.borderTop = `1px solid var(--border-color, #e5e7eb)`;
folderAddContainer.style.display = 'flex';
folderAddContainer.style.justifyContent = 'center';

const addNewFolderBtn = document.createElement('button');
Object.assign(addNewFolderBtn.style, styles.button, {
backgroundColor: 'var(--add-color, #fd7e14)',
color: 'white',
borderRadius: '4px'
});
addNewFolderBtn.textContent = '+ 新建文件夹';
addNewFolderBtn.addEventListener('click', () => {
showFolderEditDialog('', {}, (newFolderName) => {
selectedFolderName = newFolderName;
renderFolderList();
renderButtonList();
console.log(`🆕 新建文件夹 "${newFolderName}" 已添加。`);
});
});
folderAddContainer.appendChild(addNewFolderBtn);

folderPanel.appendChild(folderListContainer);
folderPanel.appendChild(folderAddContainer);

buttonListContainer = document.createElement('div');
buttonListContainer.style.flex = '1';
buttonListContainer.style.overflowY = 'auto';
buttonListContainer.style.display = 'flex';
buttonListContainer.style.flexDirection = 'column';
buttonListContainer.style.padding = '8px';

renderFolderList();
renderButtonList();

mainContainer.appendChild(folderPanel);
mainContainer.appendChild(buttonListContainer);

const footer = document.createElement('div');
footer.style.display = 'none';
dialog.appendChild(header);
dialog.appendChild(mainContainer);
dialog.appendChild(footer);

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentSettingsOverlay = overlay;

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);
};

// ===== [新增开始] =====
let currentAutomationOverlay = null;
/**
* 弹窗:自动化设置,显示所有 domainAutoSubmitSettings,并可删除、点击添加
*/
function showAutomationSettingsDialog() {
if (currentAutomationOverlay) {
closeExistingOverlay(currentAutomationOverlay);
}
const overlay = document.createElement('div');
overlay.classList.add('automation-overlay');
overlay.style.cssText = `
${Object.entries(styles.overlay).map(([k,v]) => `${k}:${v}`).join(';')};
z-index:12000;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
justify-content: center;
align-items: center;
opacity: 0;
`;

const dialog = document.createElement('div');
dialog.style.cssText = `
${Object.entries(styles.dialog).map(([k,v]) => `${k}:${v}`).join(';')};
width: 600px;
max-width: 90vw;
background-color: var(--dialog-bg, #ffffff);
`;
dialog.innerHTML = `
<h2 style="margin-top:0;">⚡ 自动化设置</h2>
<div style="text-align:right;margin-bottom:10px;">
<button id="closeAutomationBtn" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--success-color, #22c55e);
color: #fff;
">关闭并保存</button>
</div>
<div style="border:1px solid var(--border-color);padding:10px;border-radius:8px;max-height:300px;overflow:auto;">
<table style="width:100%;border-collapse:collapse;">
<thead>
<tr style="border-bottom:1px solid var(--border-color);">
<th style="text-align:left;padding:4px;">备注名称</th>
<th style="text-align:left;padding:4px;">网址</th>
<th style="text-align:left;padding:4px;">自动提交方式</th>
<th style="width:40px;"></th>
</tr>
</thead>
<tbody id="domainRuleTbody"></tbody>
</table>
</div>
<div style="margin-top:12px;text-align:right;">
<button id="addDomainRuleBtn" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--add-color, #fd7e14);
color: #fff;
">+ 添加</button>
</div>
`;

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentAutomationOverlay = overlay;

// 渲染列表
const domainRuleTbody = dialog.querySelector('#domainRuleTbody');
function renderDomainRules() {
domainRuleTbody.innerHTML = '';
buttonConfig.domainAutoSubmitSettings.forEach((rule, idx) => {
const tr = document.createElement('tr');
tr.style.borderBottom = '1px solid var(--border-color)';
tr.innerHTML = `
<td style="padding:4px;">${rule.name}</td>
<td style="padding:4px;">${rule.domain}</td>
<td style="padding:4px;">${rule.method}</td>
<td style="padding:4px;">
<button class="deleteRuleBtn" data-idx="${idx}" style="
background:none;border:none;cursor:pointer;color:var(--danger-color,#ef4444);
">🗑️</button>
</td>
`;
domainRuleTbody.appendChild(tr);
});
domainRuleTbody.querySelectorAll('.deleteRuleBtn').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
const delIdx = parseInt(btn.getAttribute('data-idx'), 10);
buttonConfig.domainAutoSubmitSettings.splice(delIdx, 1);
renderDomainRules();
});
});
}
renderDomainRules();

// 关闭并保存
dialog.querySelector('#closeAutomationBtn').addEventListener('click', () => {
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentAutomationOverlay = null;
});

// 添加新规则
dialog.querySelector('#addDomainRuleBtn').addEventListener('click', () => {
showAddDomainRuleDialog(() => {
renderDomainRules();
});
});

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);
}

/** 子弹窗:添加新域名规则 */
let currentAddDomainOverlay = null;
function showAddDomainRuleDialog(onComplete) {
if (currentAddDomainOverlay) {
closeExistingOverlay(currentAddDomainOverlay);
}
const overlay = document.createElement('div');
overlay.classList.add('add-domain-overlay');
overlay.style.cssText = `
${Object.entries(styles.overlay).map(([k, v]) => `${k}:${v}`).join(';')};
z-index:13000;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
justify-content: center;
align-items: center;
opacity: 0;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
${Object.entries(styles.dialog).map(([k, v]) => `${k}:${v}`).join(';')};
width: 400px;
max-width: 90vw;
background-color: var(--dialog-bg, #ffffff);
`;

dialog.innerHTML = `
<h3 style="margin-top:0;">添加新网址规则</h3>
<div style="display:flex;flex-direction:column;gap:8px; margin-bottom:8px;">
<label>网址:<input type="text" id="domainInput" style="
width:100%;padding:6px;border:1px solid var(--border-color);border-radius:4px;
" placeholder="例如:chatgpt.com" /></label>
<label>备注名称:<input type="text" id="domainNameInput" style="
width:100%;padding:6px;border:1px solid var(--border-color);border-radius:4px;
" placeholder="例如:ChatGPT" /></label>
<label>自动提交方式:
<select id="methodSelect" style="
width:100%;padding:6px;border:1px solid var(--border-color);border-radius:4px;
">
<option value="Enter">Enter</option>
<option value="Cmd+Enter">Cmd+Enter</option>
<option value="模拟点击提交按钮">模拟点击提交按钮</option>
</select>
</label>
</div>
<div style="text-align:right;">
<button id="cancelAddDomain" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--cancel-color,#6B7280);
color: #fff;
margin-right:8px;
">取消</button>
<button id="confirmAddDomain" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--success-color,#22c55e);
color: #fff;
">确认</button>
</div>
`;

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentAddDomainOverlay = overlay;

dialog.querySelector('#cancelAddDomain').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentAddDomainOverlay = null;
});

dialog.querySelector('#confirmAddDomain').addEventListener('click', () => {
const domainInput = dialog.querySelector('#domainInput').value.trim();
const domainName = dialog.querySelector('#domainNameInput').value.trim();
const method = dialog.querySelector('#methodSelect').value;

if (!domainInput || !domainName) {
alert("请输入网址和备注名称!");
return;
}
// 简易校验函数
if (!isValidDomainInput(domainInput)) {
alert("网址格式不正确,请仅输入域名或包含域名的链接,例如:chatgpt.com");
return;
}

buttonConfig.domainAutoSubmitSettings.push({
domain: domainInput,
name: domainName,
method: method
});
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));

closeExistingOverlay(overlay);
currentAddDomainOverlay = null;
if (onComplete) onComplete();
});

setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);
}

function isValidDomainInput(str) {
// 简易:包含'.' 不含空格 即视为有效
if (str.includes(' ')) return false;
if (!str.includes('.')) return false;
return true;
}
// ===== [新增结束] =====

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
setCSSVariables(getCurrentTheme());
updateStylesOnThemeChange();
console.log("🌓 主题模式已切换,样式已更新。");
});

const createButtonContainer = () => {
let existingContainer = document.querySelector('.folder-buttons-container');
if (existingContainer) {
// 使用updateButtonContainer来处理已存在的容器
updateButtonContainer();
return existingContainer;
}
// 创建新容器
const buttonContainer = document.createElement('div');
buttonContainer.classList.add('folder-buttons-container');
buttonContainer.style.display = 'flex';
buttonContainer.style.flexWrap = 'wrap';
buttonContainer.style.gap = '10px';
buttonContainer.style.marginTop = '0px';
buttonContainer.style.justifyContent = 'center';
buttonContainer.style.alignItems = 'center';
buttonContainer.style.padding = '6px 15px 6px 15px';
buttonContainer.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
buttonContainer.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
buttonContainer.style.borderRadius = '4px';

// 添加所有文件夹按钮
buttonConfig.folderOrder.forEach((name) => {
const config = buttonConfig.folders[name];
if (config) {
const folderButton = createFolderButton(name, config);
buttonContainer.appendChild(folderButton);
}
});

// 按顺序添加功能按钮
// 现在所有工具按钮都在 '🖱️' 文件夹内,不再直接添加
// 仅添加设置和清空按钮
buttonContainer.appendChild(createSettingsButton());
buttonContainer.appendChild(createClearButton());

return buttonContainer;
};

const updateButtonContainer = () => {
let existingContainer = document.querySelector('.folder-buttons-container');
if (existingContainer) {
// 保存所有功能按钮的引用
const settingsButton = existingContainer.querySelector('button:nth-last-child(2)');
const clearButton = existingContainer.querySelector('button:last-child');

// 清空容器
existingContainer.innerHTML = '';

// 重新添加文件夹按钮
buttonConfig.folderOrder.forEach((name) => {
const config = buttonConfig.folders[name];
if (config) {
const folderButton = createFolderButton(name, config);
existingContainer.appendChild(folderButton);
}
});

// 按正确顺序重新添加功能按钮
if (settingsButton) existingContainer.appendChild(settingsButton);
if (clearButton) existingContainer.appendChild(clearButton);

console.log("✅ 按钮栏已更新。");
} else {
console.warn("⚠️ 未找到按钮容器,无法更新按钮栏。");
}
};

const attachButtonsToTextarea = (textarea) => {
// 仅附加一次按钮容器
let buttonContainer = document.querySelector('.folder-buttons-container');
if (!buttonContainer) {
buttonContainer = createButtonContainer();
// 插入按钮容器到 textarea 的父元素之后
// 根据ChatGPT的DOM结构,可能需要调整插入位置
textarea.parentElement.insertBefore(buttonContainer, textarea.nextSibling);
console.log("✅ 按钮容器已附加到 textarea 元素。");
} else {
console.log("ℹ️ 按钮容器已存在,跳过附加。");
}
textarea.addEventListener('contextmenu', (e) => {
e.preventDefault();
});
};

let attachTimeout;
const attachButtons = () => {
if (attachTimeout) clearTimeout(attachTimeout);
attachTimeout = setTimeout(() => {
const textareas = getAllTextareas();
console.log(`🔍 扫描到 ${textareas.length} 个 textarea 或 contenteditable 元素。`);
if (textareas.length === 0) {
console.warn("⚠️ 未找到任何 textarea 或 contenteditable 元素。");
return;
}
attachButtonsToTextarea(textareas[textareas.length - 1]);
console.log("✅ 按钮已附加到最新的 textarea 或 contenteditable 元素。");
}, 300);
};

const observeShadowRoots = (node) => {
if (node.shadowRoot) {
const shadowObserver = new MutationObserver(() => {
attachButtons();
});
shadowObserver.observe(node.shadowRoot, {
childList: true,
subtree: true,
});
node.shadowRoot.querySelectorAll('*').forEach(child => observeShadowRoots(child));
}
};

const initialize = () => {
attachButtons();
const observer = new MutationObserver((mutations) => {
let triggered = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
observeShadowRoots(node);
triggered = true;
}
});
}
});
if (triggered) {
attachButtons();
console.log("🔔 DOM 发生变化,尝试重新附加按钮。");
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
console.log("🔔 MutationObserver 已启动,监听 DOM 变化。");
};

window.addEventListener('load', () => {
console.log("⏳ 页面已完全加载,开始初始化脚本。");
initialize();
});

// 动态更新样式以适应主题变化
const updateStylesOnThemeChange = () => {
// Since we're using CSS variables, the styles are updated automatically
// Just update the button container to apply new styles
updateButtonContainer();
};

// Initial setting of CSS variables
setCSSVariables(getCurrentTheme());
})();


---分割线---

一、
你是一位经验丰富的userscript开发专家,精通JavaScript和浏览器API。你擅长编写各种类型的脚本,包括但不限于网页优化、自动化任务、UI增强和数据处理。无论遇到什么样的需求或挑战,你都能分析问题并提供最优的解决方案。

如果有任何不清楚的地方,请提出问题以获取更多信息。

二、参考资料:
- 原始脚本已提供;

三、项目背景:
当前应用中的文件夹按钮栏在以下场景下存在问题:
1. **按钮数量过多**:
导致按钮栏高度动态变化,从而影响整体界面布局。
2. **移动端表现不佳**:
高度变化在移动设备上尤为明显,占用过多屏幕空间,降低用户体验。

**项目目标:**
- **固定按钮栏高度**:确保在任何按钮数量下,按钮栏高度保持一致。
- **提升跨设备友好性**:优化按钮栏在不同尺寸和类型设备上的显示效果。
- **提供用户自定义高度功能**:允许用户根据需求调整按钮栏高度。

四、任务说明
1. **固定高度**:
将文件夹按钮及其按钮栏的高度设定为固定值,
确保在任何按钮数量下高度保持一致。
2. **用户自定义高度**
- 允许用户在 50px 至 150px 范围内自定义按钮栏高度。
- 提供直观的用户界面(如滑块或输入框)供用户调整高度,并实时预览效果。
3. **横向滚动**:
- 当按钮数量超出当前浏览器窗口宽度时,启用横向滚动以查看隐藏的按钮。
- 实现平滑的滚动体验,确保兼容现代浏览器和移动设备。
4. **隐藏滚动条**
- 启用横向滚动时,隐藏滚动条,仅保留滚动功能。
- 确保隐藏滚动条后,滚动操作仍具备良好的可用性和可访问性。
5. **兼容性与性能优化**
- 确保在现代浏览器及主流移动浏览器上正常运行。
- 优化代码,减少加载时间和资源消耗,避免引入额外的性能负担。

五、注意事项:
- 确保样式美观(且自适应 普通/黑暗模式);
- 更新/完善相关的所有功能和方法;
- 保持其他功能不变,不要改动原脚本的任何其他功能!

六、标准操作程序(SOP):
第一步:深入理解该脚本的结构、功能
和目的;
第二步:把要求分解为最基础的具体改进细节;
第三步:针对每个要求,提出至少3种可行的解决方案;
第四步:评估每种解决方案的优缺点,考虑时间复杂度、空间复杂度、代码可维护性等因素,选择最佳方案;
第五步:实施选定的解决方案,逐步优化脚本;
第六步:全面测试修改后的脚本,确保功能正确且性能提升;
第七步:在完成所有改进后,
请按照以下规范,提供每一处代码修改:

1. 对每一处代码更改:
a. 使用 ```[编程语言] 格式的代码块
b. 在代码块前注明以下信息:
- 具体位置:[函数名] (行号范围)
- 修改类型:[新增/修改/删除]
- 更改说明:[简要说明更改的原因和目的]
c. 确保代码块包含足够的上下文,便于理解修改位置(至少包含修改前后的几行代码)
d. 代码块必须可直接复制使用,无需额外修改

2. 如有多处修改,请按照依赖关系排序,确保后续修改不会破坏前面的更改

3. 请包含必要的注释和文档更新建议,确保代码的可读性和可维护性

4. 示例格式:
位置:login_function (第24-30行)
修改类型:修改
更改说明:添加用户验证逻辑
```[语言]
// 包含足够上下文的代码块
```

Suy nghĩ

Fixing layout issues

I’m refining the button bar to a fixed height, ensuring it is horizontally scrollable, hides the scrollbar, and allows height adjustments between 50 and 150 px. This involves a thorough breakdown and step-by-step code changes.

Synthesizing and refining

I'm thinking through the script's structure, evaluating three solutions, and mapping out a detailed plan with code references.

Laying out the options

I’m planning to create 4-5 code blocks, ensuring each change is included with enough context and reasons, particularly focusing on setting up the fixed height for .folder-buttons-container and enabling horizontal scrolling.

Enhancing UI customization

I’m working through a UI slider for Button Bar Height, stored in buttonConfig. Horizontal scrolling is enabled with hidden scrollbars, ensuring no change to existing functionalities.

Thought about button bar optimization,持续 8 秒

Trả lời

以下答案遵循您给出的 标准操作程序(SOP),并在文末按要求提供了每一处代码修改的具体位置、修改类型、修改原因以及可直接复制使用的代码块示例。由于整份脚本较长,行号仅供参考;如在实际项目中合并,请根据实际行号进行微调。


第一步:深入理解脚本的结构、功能和目的

  1. 脚本主功能

    • 在指定页面上添加「文件夹」按钮,每个文件夹内包含若干「模板按钮」或「工具按钮」。
    • 点击模板按钮可向当前焦点的输入框(<textarea>contenteditable)注入预设文本,支持自动提交等功能。
    • 在「设置面板」中可对文件夹、按钮进行增删改,导出或导入配置等。
  2. 现有布局问题

    • 按钮数量过多时,按钮栏会随高度的增减动态变化,影响整体界面布局,且在移动端表现不佳,占用过多可视区域。
  3. 本次改动核心目的

    • 固定按钮栏高度,不随按钮数量变化。
    • 允许用户自定义按钮栏高度(在 50px 到 150px 之间)。
    • 横向滚动 以容纳更多按钮,且 隐藏滚动条

目标是不破坏其他已有功能,并与当前暗色/浅色主题样式逻辑保持兼容。


第二步:把需求分解为最基础的具体改进细节

  1. 固定高度
    • .folder-buttons-container 的高度固定,改为 height: <用户设置的数值>px;
    • 避免按钮数量过多时导致容器高度无限增长。
  2. 用户自定义高度
    • 在设置面板(或单独配置面板)增加一个滑块或输入框,可在 50px 至 150px 之间调整「按钮栏」高度。
    • 将此数值存储在 buttonConfig 中,并实时应用到按钮栏。
  3. 横向滚动 + 隐藏滚动条
    • .folder-buttons-container 启用 overflow-x: auto; overflow-y: hidden;,在需要时出现横向滚动。
    • 使用兼容方式隐藏滚动条(如 ::-webkit-scrollbar { display: none; } + scrollbar-width: none;)。
  4. 不破坏原有功能
    • 其他逻辑(如自动提交、文件夹增删改、按钮编辑等)保持不变。

第三步:针对每个要求,提出至少3种可行的解决方案

以下针对「固定高度 + 横向滚动 + 用户自定义高度 + 隐藏滚动条」一并讨论:

  1. 方案 A:直接在现有 .folder-buttons-container 上增加固定高度 + 横向滚动

    • 优点:实现简单;只需少量 CSS 修改 + 在设置面板提供一段简单的输入或滑块。
    • 缺点:需要统一为固定高度,会影响已有布局,必须小心在移动端下的自适应性。
  2. 方案 B:为 .folder-buttons-container 增加内层容器,外层管理高度,内层负责横向滚动

    • 优点:可分别管理高度和滚动,结构更清晰;内部容器可设置滚动隐藏,外层容器可设置固定高度。
    • 缺点:需要增加一层容器,改动更多 DOM 结构;但对后续维护可读性更好。
  3. 方案 C:利用 CSS Flex + max-height + 垂直滚动隐藏多余部分,再在设置面板中允许用户微调 max-height

    • 优点:只需改动 CSS;不需要手动处理横向滚动(但与原需求冲突:我们需要「横向」滚动时隐藏滚动条)。
    • 缺点:不满足本次「横向滚动」的需求;也无法有效解决按钮过多的问题(它还是会换行)。

在满足「横向滚动 + 隐藏滚动条 + 可调高度」的前提下,方案 A 实现最直接,也最易与现有脚本融合。我们将采用方案 A


第四步:评估并选择最佳方案

  • 方案 A 结合固定高度 + 横向滚动,是最直接且易行的做法,改动最少,也不会破坏原脚本的现有逻辑。
  • 我们只需在当前的 .folder-buttons-container 中:
    1. 加入固定高度及水平滚动。
    2. 在设置面板中新增「高度调节」元素,值存储于 buttonConfig 并应用到容器。
    3. 在需要时隐藏滚动条。

因此选择方案 A 作为最终实施方案。


第五步:实施选定方案,逐步优化脚本

实现要点:

  1. 数据层

    • buttonConfig 中新增一个字段: buttonBarHeight,默认值建议 80(px)。
    • 加入对上下限的校验(最小 50,最大 150)。
  2. UI 层

    • 在「设置面板」(showUnifiedSettingsDialog)或单独的「配置管理」中,添加一个输入框或滑块,用于设置 buttonBarHeight
    • 确保在值变动时,实时更新本地配置并调用 updateButtonBarHeight() 以应用新高度。
  3. 样式层

    • .folder-buttons-container
      display: flex; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; height: [buttonBarHeight]px;
    • 隐藏滚动条(兼容多浏览器):
      scrollbar-width: none; -ms-overflow-style: none; &::-webkit-scrollbar { display: none; }
  4. 调用时机

    • 每次脚本初始化(initialize())或用户更改高度后,更新容器高度、刷新布局。

第六步:全面测试修改后的脚本

  1. 桌面端浏览器

    • 不同主题下检查按钮栏是否保持固定高度,横向滚动正常,滚动条是否隐藏。
    • 调整高度在 50px ~ 150px 范围内时,有无布局冲突。
  2. 移动端

    • 同样检查横向滚动是否顺畅,滚动条隐藏是否生效。
    • 确保在极端小屏下,也可通过水平滚动查看所有按钮。
  3. 功能完整性

    • 确认「设置面板」中原有功能(增删改按钮、导入导出、自动提交设置)不受影响。

第七步:提供每一处代码修改

下面为示例修改,对应原脚本大致行号;如在您的项目中,行号需根据实际脚本进行相应微调。

注意:所有代码块请直接复制至对应位置即可;若担心冲突,可搜索相同注释或上下文以进行手动合并。


1. 在 defaultConfig 中添加新字段 buttonBarHeight

位置:靠近脚本第 ~140 行附近,// 默认配置 部分
修改类型新增
更改说明:在默认配置中增加 buttonBarHeight 字段,初始值 80(px)。

// (上下文示例) 原来的 defaultConfig 结构示意: const defaultConfig = { folders: { "默认": { /* ... */ }, "🖱️": { /* ... */ } }, folderOrder: ["默认", "🖱️"], domainAutoSubmitSettings: [ /* ... */ ] }; // ======= 新增开始 ======= defaultConfig.buttonBarHeight = 80; // ======= 新增结束 =======

2. 在脚本加载配置时,确保 buttonBarHeight 存在

位置:在 ~155 行附近,紧接读取本地存储后(buttonConfig = JSON.parse(...)
修改类型新增
更改说明:若本地无 buttonBarHeight 字段,则初始化为默认值。

// (上下文示例) let buttonConfig = JSON.parse(localStorage.getItem('chatGPTButtonFoldersConfig')) || JSON.parse(JSON.stringify(defaultConfig)); // ===== [新增开始] ===== if (typeof buttonConfig.buttonBarHeight !== 'number') { buttonConfig.buttonBarHeight = defaultConfig.buttonBarHeight; } // ===== [新增结束] =====

3. 在按钮容器 .folder-buttons-container 上设置固定高度、横向滚动、隐藏滚动条

位置:在 createButtonContainer() 函数内,约 ~1050 行附近。
修改类型修改
更改说明:将原先 flex-wrap: wrap; 改为 nowrap;,并设置 overflow-x,以及根据配置应用用户高度,隐藏滚动条。

// (上下文示例) createButtonContainer 函数 const createButtonContainer = () => { let existingContainer = document.querySelector('.folder-buttons-container'); if (existingContainer) { updateButtonContainer(); return existingContainer; } // 创建新容器 const buttonContainer = document.createElement('div'); buttonContainer.classList.add('folder-buttons-container'); // ======= 修改前 ======= // buttonContainer.style.display = 'flex'; // buttonContainer.style.flexWrap = 'wrap'; // buttonContainer.style.gap = '10px'; // buttonContainer.style.marginTop = '0px'; // ... // ======= 修改后 ======= buttonContainer.style.display = 'flex'; buttonContainer.style.flexWrap = 'nowrap'; // 改为不换行 buttonContainer.style.overflowX = 'auto'; // 横向滚动 buttonContainer.style.overflowY = 'hidden'; // 禁止纵向滚动 buttonContainer.style.gap = '10px'; buttonContainer.style.marginTop = '0px'; buttonContainer.style.height = buttonConfig.buttonBarHeight + 'px'; // ===== [新增] 隐藏滚动条 (兼容性写法) ===== buttonContainer.style.scrollbarWidth = 'none'; // for Firefox buttonContainer.style.msOverflowStyle = 'none'; // for IE/Edge buttonContainer.classList.add('hide-scrollbar'); // 用于自定义::-webkit-scrollbar buttonContainer.style.justifyContent = 'center'; buttonContainer.style.alignItems = 'center'; buttonContainer.style.padding = '6px 15px'; buttonContainer.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)'; buttonContainer.style.backgroundColor = 'var(--button-bg, #f3f4f6)'; buttonContainer.style.borderRadius = '4px'; // 在后面继续原逻辑...

并在脚本的 <style> 或动态插入里,为 hide-scrollbar 类添加 ::-webkit-scrollbar 隐藏声明:

// (示例) 在合适位置添加: const styleEl = document.createElement('style'); styleEl.innerHTML = ` .hide-scrollbar::-webkit-scrollbar { display: none !important; } `; document.head.appendChild(styleEl);

4. 封装更新按钮栏高度的函数并在设置面板中增加滑块或输入框

位置:在 showUnifiedSettingsDialog() 函数(大约 ~1600 行)内部新增
修改类型新增
更改说明

  1. 新增 updateButtonBarHeight(newHeight) 函数,在容器上动态更新高度。
  2. 在设置面板中添加一个滑块或输入框,用于设置 buttonBarHeight(50~150),并实时保存和预览。
// 1) [新增] 在脚本内增加一个更新函数,放在合适位置(例如 showUnifiedSettingsDialog 上方): function updateButtonBarHeight(newHeight) { const clamped = Math.min(150, Math.max(50, newHeight)); // 限制范围 buttonConfig.buttonBarHeight = clamped; localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig)); // 更新容器高度 const container = document.querySelector('.folder-buttons-container'); if (container) { container.style.height = clamped + 'px'; } console.log("🔧 按钮栏高度已更新为", clamped, "px"); } // 2) 在 showUnifiedSettingsDialog 函数的对话框内,添加 UI 控件 const showUnifiedSettingsDialog = () => { // ... 原有代码 ... // [在 header 或 mainContainer 的合适位置添加一个高度调节区域] const heightControlContainer = document.createElement('div'); heightControlContainer.style.display = 'flex'; heightControlContainer.style.alignItems = 'center'; heightControlContainer.style.marginBottom = '8px'; const heightLabel = document.createElement('label'); heightLabel.textContent = "按钮栏高度 (50~150):"; heightLabel.style.marginRight = '8px'; const heightInput = document.createElement('input'); heightInput.type = 'number'; heightInput.min = '50'; heightInput.max = '150'; heightInput.value = buttonConfig.buttonBarHeight; // 初始值 heightInput.style.width = '60px'; heightInput.addEventListener('change', (e) => { const value = parseInt(e.target.value, 10); updateButtonBarHeight(value); }); heightControlContainer.appendChild(heightLabel); heightControlContainer.appendChild(heightInput); // 将这个容器插入到设置面板中合适位置,例如 header 下面 header.appendChild(heightControlContainer); // ... 其余原代码不变 ... };

提示:也可以使用 <input type="range"> 滑块形式,将 heightInput.type = 'range',并加监听 input 事件实时预览。


5. 其余功能确认不变

  • 不影响自动提交、文件夹增删改、工具按钮等原有流程。

总结

通过以上改动:

  1. 按钮栏高度由 buttonBarHeight(默认 80px)控制,不再随按钮数量动态变高。
  2. 用户可在设置面板中调节高度(范围 50~150)。
  3. .folder-buttons-container 支持横向滚动并隐藏滚动条。
  4. 保持原脚本的其他逻辑与功能不变,满足项目需求并兼容暗黑/浅色模式。

若尚需更多个性化扩展(如在移动端时采用更低/更高默认值、增加拖拽滚动效果等),可在此基础上进一步拓展。祝您开发顺利!

Câu hỏi

之前的第4步不太符合我的要求,请根据以下具体要求进行调整:
1. 在设置面板 自动化按钮和 配置管理 按钮之间,添加一个新按钮,命名为“样式管理”。
2. 点击该“样式管理”按钮后,弹出一个自定义样式修改的弹窗。
3. 在该弹窗中,允许用户修改自定义按钮的高度;

发现问题:
刚刚的修改,会造成窗口宽度不够时,文件夹按钮中的文本会垂直显示的问题;
问题修复的具体要求:
问题:窗口宽度不足时,文件夹按钮中的文本会垂直显示;
要求行为:文本必须始终保持水平对齐;
范围:脚本文件夹按钮栏中显示的所有文件夹按钮;

---分割线---


请按照以下规范,提供每一处代码修改:

1. 对每一处代码更改:
a. 使用 ```[编程语言] 格式的代码块
b. 在代码块前注明以下信息:
- 具体位置:[函数名] (行号范围)
- 修改类型:[新增/修改/删除]
- 更改说明:[简要说明更改的原因和目的]
c. 确保代码块包含足够的上下文,便于理解修改位置(至少包含修改前后的几行代码)
d. 代码块必须可直接复制使用,无需额外修改

2. 如有多处修改,请按照依赖关系排序,确保后续修改不会破坏前面的更改

3. 请包含必要的注释和文档更新建议,确保代码的可读性和可维护性

4. 示例格式:
位置:login_function (第24-30行)
修改类型:修改
更改说明:添加用户验证逻辑
```[语言]
// 包含足够上下文的代码块
```

Suy nghĩ

Requesting button adjustment

The user wants a new "样式管理" button, a height-adjustable panel, and a solution for vertical text due to narrow windows. Code changes and disclaimers are needed.

Enhancing button text visibility

To prevent vertical text, using "white-space: nowrap;" and ensuring minimal button width is key. Display settings like "width: fit-content" or "display: inline-block" can also be considered.

Implementing changes step by step

First, integrate "styleMgmtBtn" into "headerBtnsWrapper". Then, open a dialog for setting button height. Next, remove this setting from the main panel. Finally, address vertical text with "white-space: nowrap;".

Setting up and refining

I’m placing "样式管理" in the dialog, adding a height adjustment overlay, tweaking the container, and using CSS to keep text horizontal in folder buttons.

Tweaking button setup

Removing height input from main panel, creating height adjustment function, and addressing vertical text with specific CSS styles for folder button whitespace and overflow.

思考,持续 11 秒

Trả lời

以下为根据您最新需求所做的逐条修改,包含了在「自动化」和「配置管理」按钮之间添加「样式管理」按钮、新建「样式管理」弹窗、并修复因窗口宽度不足导致文本垂直显示的问题。请仔细核对行号和位置;如与您项目的实际行号不符,可根据搜索同名函数或注释进行手动合并。


1. 在「设置面板」中新增“样式管理”按钮

位置showUnifiedSettingsDialog (第1600-1630行)
修改类型修改
更改说明:在“自动化”按钮和“配置管理”按钮之间,新增一个“样式管理”按钮,并为其绑定点击事件 showStyleSettingsDialog()

// ========== 代码示例开始 (上下文: showUnifiedSettingsDialog 函数) ========== // 原有的 headerBtnsWrapper 及其内部按钮列表: const headerBtnsWrapper = document.createElement('div'); headerBtnsWrapper.style.display = 'flex'; headerBtnsWrapper.style.gap = '10px'; // 自动化按钮 const automationBtn = document.createElement('button'); automationBtn.innerText = '⚡ 自动化'; automationBtn.type = 'button'; automationBtn.style.backgroundColor = 'var(--info-color, #4F46E5)'; automationBtn.style.color = 'white'; automationBtn.style.border = 'none'; automationBtn.style.borderRadius = '4px'; automationBtn.style.padding = '5px 10px'; automationBtn.style.cursor = 'pointer'; automationBtn.style.fontSize = '14px'; automationBtn.addEventListener('click', () => { showAutomationSettingsDialog(); }); headerBtnsWrapper.appendChild(automationBtn); // ===== [新增开始] ===== // 样式管理按钮 const styleMgmtBtn = document.createElement('button'); styleMgmtBtn.innerText = '🎨 样式管理'; styleMgmtBtn.type = 'button'; styleMgmtBtn.style.backgroundColor = 'var(--info-color, #4F46E5)'; styleMgmtBtn.style.color = 'white'; styleMgmtBtn.style.border = 'none'; styleMgmtBtn.style.borderRadius = '4px'; styleMgmtBtn.style.padding = '5px 10px'; styleMgmtBtn.style.cursor = 'pointer'; styleMgmtBtn.style.fontSize = '14px'; styleMgmtBtn.addEventListener('click', () => { showStyleSettingsDialog(); }); headerBtnsWrapper.appendChild(styleMgmtBtn); // ===== [新增结束] ===== // 原有配置管理按钮 const openConfigBtn = createConfigSettingsButton(); headerBtnsWrapper.appendChild(openConfigBtn); // 原有保存关闭按钮 const saveSettingsBtn = document.createElement('button'); saveSettingsBtn.textContent = '💾 关闭并保存'; // ...后续原有样式及逻辑不变 ...

2. 新增“样式管理”弹窗并让用户修改按钮栏高度

位置:在脚本结尾处或与其他弹窗函数同级(例如第1700-1780行)
修改类型新增
更改说明:新增 showStyleSettingsDialog() 函数;用户可在此弹窗修改按钮栏高度并实时预览。原有在主面板中直接修改高度的逻辑可移除(若之前添加过)。

// ========== 代码示例开始 (新增函数 showStyleSettingsDialog) ========== /** * 用于弹出“样式管理”对话框,让用户设置按钮栏高度等样式。 */ function showStyleSettingsDialog() { // 如果已有同名弹窗则先关闭(防止重复) if (currentConfirmOverlay) { closeExistingOverlay(currentConfirmOverlay); } // 创建覆盖层 const overlay = document.createElement('div'); overlay.classList.add('style-overlay'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5)); backdrop-filter: blur(2px); z-index: 12000; display: flex; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.3s ease; `; // 创建对话框容器 const dialog = document.createElement('div'); dialog.style.cssText = ` background-color: var(--dialog-bg, #ffffff); color: var(--text-color, #333333); border-radius: 4px; padding: 24px; box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1)); border: 1px solid var(--border-color, #e5e7eb); transition: transform 0.3s ease, opacity 0.3s ease; width: 400px; max-width: 90vw; `; // 标题 const title = document.createElement('h3'); title.textContent = '🎨 样式管理'; title.style.marginTop = '0'; dialog.appendChild(title); // 提示文字 const desc = document.createElement('p'); desc.textContent = '调整下方选项,可自定义按钮栏的外观样式:'; dialog.appendChild(desc); // 创建高度调节容器 const heightControlContainer = document.createElement('div'); heightControlContainer.style.marginBottom = '16px'; heightControlContainer.style.display = 'flex'; heightControlContainer.style.alignItems = 'center'; heightControlContainer.style.gap = '8px'; const heightLabel = document.createElement('label'); heightLabel.textContent = '按钮栏高度 (50 ~ 150):'; heightLabel.style.whiteSpace = 'nowrap'; const heightInput = document.createElement('input'); heightInput.type = 'number'; heightInput.min = '50'; heightInput.max = '150'; heightInput.value = buttonConfig.buttonBarHeight; heightInput.style.width = '60px'; heightInput.addEventListener('change', () => { const val = parseInt(heightInput.value, 10); const clamped = Math.min(150, Math.max(50, val)); buttonConfig.buttonBarHeight = clamped; localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig)); // 实时更新容器高度 const container = document.querySelector('.folder-buttons-container'); if (container) { container.style.height = clamped + 'px'; } console.log(`✅ 按钮栏高度更新为 ${clamped}px`); }); heightControlContainer.appendChild(heightLabel); heightControlContainer.appendChild(heightInput); dialog.appendChild(heightControlContainer); // 底部按钮区域 const footer = document.createElement('div'); footer.style.display = 'flex'; footer.style.justifyContent = 'flex-end'; footer.style.gap = '12px'; const closeBtn = document.createElement('button'); closeBtn.textContent = '关闭'; closeBtn.style.cssText = ` background-color: var(--cancel-color, #6B7280); color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-size: 14px; `; closeBtn.addEventListener('click', () => { closeExistingOverlay(overlay); currentConfirmOverlay = null; }); footer.appendChild(closeBtn); dialog.appendChild(footer); overlay.appendChild(dialog); document.body.appendChild(overlay); currentConfirmOverlay = overlay; // 动画进入 setTimeout(() => { overlay.style.opacity = '1'; dialog.style.transform = 'scale(1)'; }, 10); } // ========== 代码示例结束 (showStyleSettingsDialog) ==========

如果您之前在 showUnifiedSettingsDialog 中已有与“按钮栏高度”相关的输入或滑块,请酌情删除,避免冲突。


3. 修复文件夹按钮文本在窗口过窄时垂直显示的问题

位置createFolderButton (第720-740行) 或 createFolderButtonElement 函数中,为文件夹按钮设置 white-space: nowrap 等样式。
修改类型修改
更改说明:给文件夹按钮加上「不换行、保持水平对齐」的特性,防止文本堆叠到纵向。若您在脚本中是使用 createFolderButtonElement 方法设置按钮样式,请修改相应位置即可。

// ========== 代码示例开始 (上下文: createFolderButton / createFolderButtonElement) ========== // 以 createFolderButtonElement() 为例: const createFolderButtonElement = (folderName, folderConfig) => { const folderButton = document.createElement('button'); folderButton.innerText = folderName; folderButton.type = 'button'; folderButton.style.backgroundColor = folderConfig.color; folderButton.style.color = folderConfig.textColor || '#ffffff'; folderButton.style.border = 'none'; folderButton.style.borderRadius = '4px'; folderButton.style.padding = '6px 12px'; folderButton.style.cursor = 'pointer'; folderButton.style.fontSize = '14px'; folderButton.style.fontWeight = '500'; folderButton.style.transition = 'all 0.2s ease'; folderButton.style.position = 'relative'; // ===== [修改前] ===== // folderButton.style.display = 'flex'; // ===== [修改后]:确保不换行、水平对齐 folderButton.style.display = 'inline-flex'; folderButton.style.alignItems = 'center'; folderButton.style.whiteSpace = 'nowrap'; // 若有需要,可以加: // folderButton.style.overflow = 'hidden'; // folderButton.style.textOverflow = 'ellipsis'; // folderButton.style.maxWidth = '200px'; // 可设一个最大宽度 folderButton.classList.add('folder-button'); // ... 其余逻辑不变 ... return folderButton; };

如果在您的脚本中创建文件夹按钮的函数名称不同,请根据实际名称进行相应修改,但核心思路是在按钮元素上添加 inline-flex + white-space: nowrap


4. (如有)删除之前在主设置面板中添加“按钮栏高度”输入的相关代码

仅在您已经在主设置面板中添加过高度设置的情况下需要此步骤。否则可忽略。
以下为示例,假设之前我们在 showUnifiedSettingsDialog 中添加过名为 heightControlContainer 的代码。

位置showUnifiedSettingsDialog (第1650-1680行)
修改类型删除
更改说明:去除原来在主面板直接设置「按钮栏高度」的 UI,避免与新建的“样式管理”弹窗冲突。

// ========== 代码示例开始 (上下文: showUnifiedSettingsDialog 中如有 heightControlContainer) ========== // ===== [删除前] ===== // const heightControlContainer = document.createElement('div'); // heightControlContainer.style.display = 'flex'; // heightControlContainer.style.alignItems = 'center'; // heightControlContainer.style.marginBottom = '8px'; // const heightLabel = document.createElement('label'); // heightLabel.textContent = "按钮栏高度 (50~150):"; // heightControlContainer.appendChild(heightLabel); // const heightInput = document.createElement('input'); // heightInput.type = 'number'; // heightInput.min = '50'; // heightInput.max = '150'; // heightInput.value = buttonConfig.buttonBarHeight; // // ... // heightControlContainer.appendChild(heightInput); // header.appendChild(heightControlContainer); // ===== [删除结束] =====

如上所示,将该片段整体删除或注释掉,以确保此处与“样式管理”弹窗不会冲突、重复。


文档更新建议

  1. 新增说明

    • 「样式管理」弹窗用于管理按钮栏高度等界面外观;用户可在此实时修改并预览。
    • 在「设置面板」点击「样式管理」按钮时打开,设置范围为 50~150px。
    • 「自动化」「配置管理」等原有功能保持不变。
  2. 维护要点

    • 如需后续添加更多界面样式(例如文件夹按钮圆角大小等),可直接在 showStyleSettingsDialog() 里新增表单控件即可。
    • 如在移动端想要不同的最小/最大高度,可在 heightInput 的校验逻辑里再做平台检测并调整上下限。

至此,根据您的需求,“样式管理”按钮及弹窗已添加,文件夹按钮的文本在窗口过窄时也会保持水平,不再垂直堆叠。若有任何不明之处,欢迎进一步沟通。祝您开发顺利!

Câu hỏi

新要求:
基于“自动化设置”弹窗的设计风格,优化“样式管理”按钮对应的自定义样式修改弹窗。具体需求如下:
1. **新增功能**:
- **备注名称**:为每个网址添加备注,便于用户标识和管理。
- **网址编辑**:显示当前应用自定义样式的网址,并允许用户编辑。
- **高度设置**:升级现有的高度设置功能,使其能够根据不同网址进行配置。
- **其他自定义样式**:允许用户为指定的网址注入自定义的CSS样式代码。

2. **新建按钮功能**:
- 在弹窗中新增“新建”按钮,点击后弹出新建样式的弹窗。
- 新建对话框默认填充当前网址及其网站名称,简化用户创建新自定义样式的流程。


---分割线---


请按照以下规范,提供每一处代码修改:

1. 对每一处代码更改:
a. 使用 ```[编程语言] 格式的代码块
b. 在代码块前注明以下信息:
- 具体位置:[函数名] (行号范围)
- 修改类型:[新增/修改/删除]
- 更改说明:[简要说明更改的原因和目的]
c. 确保代码块包含足
够的上下文,便于理解修改位置(至少包含修改前后的几行代码)
d. 代码块必须可直接复制使用,无需额外修改

2. 如有多处修改,请按照依赖关系排序,确保后续修改不会破坏前面的更改

3. 请包含必要的注释和文档更新建议,确保代码的可读性和可维护性

4. 示例格式:
位置:login_function (第24-30行)
修改类型:修改
更改说明:添加用户验证逻辑
```[语言]
// 包含足够上下文的代码块
```

Suy nghĩ

Mapping out the feature

I’m working through expanding the "样式管理" button to include domain-based custom styles like "备注名称," "网址编辑," "高度设置," and "注入自定义CSS." This involves creating a table with columns for each domain's details.

Streamlining user input

I'm aligning the process to collect new domain styles, ensuring settings are stored appropriately in buttonConfig.domainStyleSettings. This involves adding a new config array and refining the showStyleSettingsDialog function.

Revisiting the interface

I’m thinking through a table-based approach in the "样式管理" popup, including options to add, edit, and delete domain style rules, and possibly inject custom CSS for added flexibility.

Ensuring code consistency

I’m ensuring the use of the code block pattern, including tracking modifications, updating documentation, and managing "domainStyleSettings" to store domain-specific style configurations effectively.

思考,持续 10 秒

Trả lời

以下为满足您最新需求所做的分步修改示例,包括:

  1. 在配置中添加新的域名样式设置数组

  2. 新建或修改 “样式管理” 弹窗:

    • 模仿“自动化设置”弹窗的布局风格,使用表格显示各域名样式配置
    • 支持 备注名称网址高度设置自定义CSS
    • 提供“编辑 / 删除”功能按钮
    • 添加“新建”功能,在弹窗内创建新的域名样式规则,并默认填充当前网址及备注名称
  3. 在适当时机应用每个域名对应的高度和 CSS 注入(示例仅提供注入思路,可结合您已有的脚本逻辑进行强化)

请注意,以下行号和函数名基于示例脚本进行说明,如与您的实际脚本不一致,可通过搜索相应注释或函数名称进行合并。所有代码块皆可直接复制粘贴,无需额外调整。


1. 在默认配置中新增 domainStyleSettings 字段

位置defaultConfig (第140-155行)
修改类型新增
更改说明:在全局默认配置中新建 domainStyleSettings 数组,用于存储各网址的样式设置(备注、域名、高度、自定义CSS)。

// =========== (上下文: defaultConfig 定义附近) =========== // 默认配置 const defaultConfig = { folders: { /* ... */ }, folderOrder: ["默认", "🖱️"], domainAutoSubmitSettings: [ /* ... */ ], // ===== [新增开始] ===== /** * domainStyleSettings: 数组,每个元素结构示例: * { * domain: "chatgpt.com", * name: "ChatGPT自定义样式", * height: 90, * cssCode: ".some-class { color: red; }" * } */ domainStyleSettings: [] // ===== [新增结束] ===== };

2. 读取/初始化 domainStyleSettings

位置:大约在第155-170行,对 buttonConfig 进行本地存储读取后检查字段
修改类型新增
更改说明:若无 domainStyleSettings 字段,则初始化为空数组。

// =========== (上下文: 加载 buttonConfig 后) =========== let buttonConfig = JSON.parse(localStorage.getItem('chatGPTButtonFoldersConfig')) || JSON.parse(JSON.stringify(defaultConfig)); // ===== [新增开始] ===== if (!Array.isArray(buttonConfig.domainStyleSettings)) { buttonConfig.domainStyleSettings = []; } // ===== [新增结束] =====

3. 重构或新增「样式管理」弹窗,表格方式展示域名样式

位置showStyleSettingsDialog (第1800-1900行)
修改类型修改(若您已有简版弹窗,需要替换/重构);若还没有该函数,则为新增
更改说明:使用与“自动化设置”相似的表格布局,列出 domainStyleSettings 中的所有规则,支持“备注名称”、“网址”、“高度(px)”、“自定义CSS”、“操作(删除/编辑)”。并增加“新建”按钮。

// =========== (上下文: showStyleSettingsDialog 函数) =========== // 注意:如已有同名函数,请整体替换为下述版本 function showStyleSettingsDialog() { // 若已存在同类弹窗则先关闭 if (currentConfirmOverlay) { closeExistingOverlay(currentConfirmOverlay); } // 创建覆盖层 const overlay = document.createElement('div'); overlay.classList.add('style-overlay'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5)); backdrop-filter: blur(2px); z-index: 12000; display: flex; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.3s ease; `; // 创建对话框 const dialog = document.createElement('div'); dialog.style.cssText = ` background-color: var(--dialog-bg, #ffffff); color: var(--text-color, #333333); border-radius: 4px; padding: 24px; box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1)); border: 1px solid var(--border-color, #e5e7eb); transition: transform 0.3s ease, opacity 0.3s ease; width: 600px; max-width: 95vw; max-height: 80vh; overflow-y: auto; `; // 标题 const title = document.createElement('h2'); title.textContent = '🎨 样式管理'; title.style.marginTop = '0'; dialog.appendChild(title); // 说明文字 const desc = document.createElement('p'); desc.textContent = '您可根据不同网址,自定义按钮栏高度和注入CSS样式。'; dialog.appendChild(desc); // 表格容器 const tableContainer = document.createElement('div'); tableContainer.style.cssText = ` border: 1px solid var(--border-color); padding: 10px; border-radius: 8px; max-height: 300px; overflow:auto; margin-bottom: 12px; `; // 表格 const styleTable = document.createElement('table'); styleTable.style.width = '100%'; styleTable.style.borderCollapse = 'collapse'; // Thead const thead = document.createElement('thead'); thead.innerHTML = ` <tr style="border-bottom:1px solid var(--border-color);"> <th style="text-align:left;padding:4px;">备注名称</th> <th style="text-align:left;padding:4px;">网址</th> <th style="text-align:left;padding:4px;">高度(px)</th> <th style="text-align:left;padding:4px;">自定义CSS</th> <th style="width:40px;"></th> </tr> `; styleTable.appendChild(thead); // Tbody const tbody = document.createElement('tbody'); // 渲染 domainStyleSettings function renderDomainStyles() { tbody.innerHTML = ''; buttonConfig.domainStyleSettings.forEach((item, idx) => { const tr = document.createElement('tr'); tr.style.borderBottom = '1px solid var(--border-color)'; tr.innerHTML = ` <td style="padding:4px;">${item.name || ''}</td> <td style="padding:4px;">${item.domain || ''}</td> <td style="padding:4px;">${item.height || ''}</td> <td style="padding:4px;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${(item.cssCode || '').replace(/"/g, '&quot;')}"> ${(item.cssCode || '').substring(0, 30).replace(/\n/g, ' ')}... </td> <td style="padding:4px;"> <button class="editStyleBtn" data-idx="${idx}" style=" background:none;border:none;cursor:pointer;color:var(--primary-color,#3B82F6); ">✏️</button> <button class="deleteStyleBtn" data-idx="${idx}" style=" background:none;border:none;cursor:pointer;color:var(--danger-color,#ef4444); ">🗑️</button> </td> `; tbody.appendChild(tr); }); // 绑定删除和编辑事件 tbody.querySelectorAll('.deleteStyleBtn').forEach(btn => { btn.addEventListener('click', (e) => { const index = parseInt(btn.getAttribute('data-idx'), 10); buttonConfig.domainStyleSettings.splice(index, 1); localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig)); renderDomainStyles(); // 重新渲染 }); }); tbody.querySelectorAll('.editStyleBtn').forEach(btn => { btn.addEventListener('click', () => { const index = parseInt(btn.getAttribute('data-idx'), 10); showEditDomainStyleDialog(index); }); }); } renderDomainStyles(); styleTable.appendChild(tbody); tableContainer.appendChild(styleTable); dialog.appendChild(tableContainer); // 新建按钮 const addStyleBtn = document.createElement('button'); addStyleBtn.textContent = '+ 新建'; addStyleBtn.style.cssText = ` background-color: var(--add-color, #fd7e14); color: #fff; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; margin-bottom: 12px; `; addStyleBtn.addEventListener('click', () => { showEditDomainStyleDialog(); // 无参表示新建 }); dialog.appendChild(addStyleBtn); // 关闭按钮 const closeBtn = document.createElement('button'); closeBtn.textContent = '关闭'; closeBtn.style.cssText = ` background-color: var(--cancel-color, #6B7280); color: white; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; float: right; `; closeBtn.addEventListener('click', () => { closeExistingOverlay(overlay); currentConfirmOverlay = null; }); dialog.appendChild(closeBtn); // 将对话框加到覆盖层 overlay.appendChild(dialog); document.body.appendChild(overlay); currentConfirmOverlay = overlay; // 动画进入 setTimeout(() => { overlay.style.opacity = '1'; dialog.style.transform = 'scale(1)'; }, 10); /** * 新建/编辑域名样式对话框 * @param {number} index - 可选,若存在则为编辑,否则新建 */ function showEditDomainStyleDialog(index) { if (currentAddDomainOverlay) { closeExistingOverlay(currentAddDomainOverlay); } const overlay2 = document.createElement('div'); overlay2.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5)); backdrop-filter: blur(2px); z-index: 13000; display: flex; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.3s ease; `; const dialog2 = document.createElement('div'); dialog2.style.cssText = ` background-color: var(--dialog-bg, #ffffff); color: var(--text-color, #333333); border-radius: 4px; padding: 24px; box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1)); border: 1px solid var(--border-color, #e5e7eb); transition: transform 0.3s ease, opacity 0.3s ease; width: 400px; max-width: 90vw; `; const isEdit = (typeof index === 'number'); const styleItem = isEdit ? { ...buttonConfig.domainStyleSettings[index] } : { domain: window.location.hostname, // 默认填充当前网址 name: document.title || '新样式', // 默认填充网页标题 height: 80, cssCode: '' }; const title2 = document.createElement('h3'); title2.textContent = isEdit ? '编辑自定义样式' : '新建自定义样式'; dialog2.appendChild(title2); // 备注名称 const nameLabel = document.createElement('label'); nameLabel.textContent = '备注名称:'; const nameInput = document.createElement('input'); nameInput.type = 'text'; nameInput.value = styleItem.name; nameInput.style.cssText = ` width:100%;margin-bottom:8px;padding:6px;border:1px solid var(--border-color);border-radius:4px; `; dialog2.appendChild(nameLabel); dialog2.appendChild(nameInput); // 网址 const domainLabel = document.createElement('label'); domainLabel.textContent = '网址:'; const domainInput = document.createElement('input'); domainInput.type = 'text'; domainInput.value = styleItem.domain; domainInput.style.cssText = ` width:100%;margin-bottom:8px;padding:6px;border:1px solid var(--border-color);border-radius:4px; `; dialog2.appendChild(domainLabel); dialog2.appendChild(domainInput); // 高度 const heightLabel = document.createElement('label'); heightLabel.textContent = '按钮栏高度(px):'; const heightInput = document.createElement('input'); heightInput.type = 'number'; heightInput.min = '50'; heightInput.max = '150'; heightInput.value = styleItem.height; heightInput.style.cssText = ` width:100%;margin-bottom:8px;padding:6px;border:1px solid var(--border-color);border-radius:4px; `; dialog2.appendChild(heightLabel); dialog2.appendChild(heightInput); // 自定义CSS const cssLabel = document.createElement('label'); cssLabel.textContent = '自定义CSS:'; const cssTextarea = document.createElement('textarea'); cssTextarea.value = styleItem.cssCode; cssTextarea.style.cssText = ` width:100%;height:80px;margin-bottom:8px;padding:6px;border:1px solid var(--border-color); border-radius:4px;resize:vertical; `; dialog2.appendChild(cssLabel); dialog2.appendChild(cssTextarea); // 底部按钮 const footer2 = document.createElement('div'); footer2.style.textAlign = 'right'; footer2.style.display = 'flex'; footer2.style.justifyContent = 'flex-end'; footer2.style.gap = '12px'; const cancelBtn2 = document.createElement('button'); cancelBtn2.textContent = '取消'; cancelBtn2.style.cssText = ` background-color: var(--cancel-color, #6B7280); color: #fff; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; `; cancelBtn2.addEventListener('click', () => { closeExistingOverlay(overlay2); }); const saveBtn2 = document.createElement('button'); saveBtn2.textContent = isEdit ? '保存' : '创建'; saveBtn2.style.cssText = ` background-color: var(--success-color,#22c55e); color: #fff; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; `; saveBtn2.addEventListener('click', () => { // 更新/创建 const updatedItem = { domain: domainInput.value.trim(), name: nameInput.value.trim() || '未命名样式', height: parseInt(heightInput.value, 10) || 80, cssCode: cssTextarea.value }; // 校验省略,简单示例 if (isEdit) { buttonConfig.domainStyleSettings[index] = updatedItem; } else { buttonConfig.domainStyleSettings.push(updatedItem); } localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig)); closeExistingOverlay(overlay2); renderDomainStyles(); // 重新加载表格 }); footer2.appendChild(cancelBtn2); footer2.appendChild(saveBtn2); dialog2.appendChild(footer2); overlay2.appendChild(dialog2); document.body.appendChild(overlay2); currentAddDomainOverlay = overlay2; setTimeout(() => { overlay2.style.opacity = '1'; dialog2.style.transform = 'scale(1)'; }, 10); } }

4. (可选)在脚本初始化处注入对应的域名CSS和高度

为了让用户在该弹窗中配置的“高度”和“自定义CSS”真正生效,可在页面初始化或 URL 变化时,检查 buttonConfig.domainStyleSettings,若当前 URL 匹配配置中的 domain,则:

  1. 注入对应CSS
  2. 把按钮栏高度更新为相应值(若需要覆盖通用高度)

位置initialize() 或与 DOM 观察相关的逻辑中 (大约第1900-1950行)
修改类型新增
更改说明:示例仅演示一个最简方式:在脚本加载完成后,检查是否匹配当前域名/URL,若匹配就插入 style 标签并设定按钮栏高度。

// =========== (上下文: initialize() 或类似函数) =========== // 在 initialize() 末尾或合适位置,新增此逻辑 (function applyDomainStyles() { const currentHost = window.location.hostname; // 找到第一个匹配的 domainStyleSettings (如需多个匹配,逻辑可自行修改) const matchedStyle = buttonConfig.domainStyleSettings.find(s => currentHost.includes(s.domain)); if (matchedStyle) { // 1) 更新按钮栏高度 const container = document.querySelector('.folder-buttons-container'); if (container) { const clamped = Math.min(150, Math.max(50, matchedStyle.height || 80)); container.style.height = clamped + 'px'; console.log(`✅ 已根据 ${matchedStyle.name} 设置按钮栏高度:${clamped}px`); } // 2) 注入自定义CSS if (matchedStyle.cssCode) { const styleEl = document.createElement('style'); styleEl.setAttribute('data-domain-style', matchedStyle.domain); styleEl.innerHTML = matchedStyle.cssCode; document.head.appendChild(styleEl); console.log(`✅ 已注入自定义CSS至 <head> 来自:${matchedStyle.name}`); } } })();

说明

  • 如果您期望同时匹配多个规则(例如同一域名有多条配置),可将 find() 改为 filter() 并遍历所有匹配项目。
  • 若需动态侦听域名变化(如 SPA 场景),可在 DOM 监听或路由切换后再次调用此函数。
  • 也可以在用户关闭弹窗后立即应用,这完全取决于您的需求。

5. 文档与维护要点

  • 配置字段说明

    • domainStyleSettings[]:每项含 domain, name, height, cssCode
    • 用户可在“样式管理”弹窗查看/新建/编辑/删除对应网址的样式设置。
  • 应用时机

    • 在脚本初始化或路由变化时,对比当前 window.location.hostname,注入自定义CSS和更新按钮栏高度。
  • 扩展

    • 可扩充更多样式字段,如字体大小、颜色等;在相应弹窗添加输入控件,保存至 domainStyleSettings 即可。

以上即为完整的修改示例,请结合您现有脚本结构进行合并或微调。若您在实际合并中有任何冲突或疑问,欢迎继续交流。祝开发顺利!

Câu hỏi

下面是我按照要求修改后的脚本:
// ==UserScript==
// @name [Chat] Template Text Folders [20250118] V2.2
// @namespace 0_V userscripts/[Chat] Template Text Folders
// @version [20250118] V2.2
// @description 在AI页面上添加预设文本文件夹和按钮,提升输入效率。优化“自动提交”开关的位置,将其移至新的“提交设置”子页面,提高用户操作便捷性。
// @update-log 移动“自动提交”开关至“提交设置”子标签页,优化界面布局,提高操作便捷性。
// @match https://chatgpt.com/*
// @match https://claude.ai/*
// @match https://chat.deepseek.com/*
// @match https://ai.donewell.cc/*
// @exclude https://ai.donewell.cc/admin/*
// @match https://free.share-ai.top/*
// @exclude https://free.share-ai.top/user-new/*
// @match https://app.chathub.gg/*
// @exclude https://app.chathub.gg/profile
// @match https://poe.com/*
// @exclude https://poe.com/settings*
// @exclude https://poe.com/explore*
// @match https://lmarena.ai/*
// @ match https://linux.do/*
// @match https://monica.im/home*
// @match https://*.aivvm.*/*
// @match https://claude.mynanian.top/*
// @match https://chat01.ai/*
// @grant none
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxODAiIGhlaWdodD0iMTQ1Ij48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTMwIiBoZWlnaHQ9IjQwIiByeD0iOCIgcnk9IjgiIGZpbGw9IiNmMDQ4NDgiLz48dGV4dCB4PSI2NSIgeT0iMjAiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIyNCIgZmlsbD0iI2ZmZmZmZiI+VGVtcGxhdGU8L3RleHQ+PHJlY3QgeD0iMCIgeT0iNTAiIHdpZHRoPSI4MCIgaGVpZ2h0PSI0MCIgcng9IjgiIHJ5PSI4IiBmaWxsPSIjNDI4NWY0Ii8+PHRleHQgeD0iNDAiIHk9IjcwIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMjQiIGZpbGw9IiNmZmZmZmYiPlRleHQ8L3RleHQ+PHJlY3QgeD0iMCIgeT0iMTAwIiB3aWR0aD0iMTEwIiBoZWlnaHQ9IjQwIiByeD0iOCIgcnk9IjgiIGZpbGw9IiMzNGE4NTMiLz48dGV4dCB4PSI1NSIgeT0iMTIwIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMjQiIGZpbGw9IiNmZmZmZmYiPkZvbGRlcnM8L3RleHQ+PC9zdmc+
// ==/UserScript==

(function () {
'use strict';

console.log("🎉 [Chat] Template Text Folders [20250118] V2.2");

// 主题样式配置(使用CSS变量)
const setCSSVariables = (currentTheme) => {
const root = document.documentElement;
root.style.setProperty('--folder-bg', currentTheme.folderBg);
root.style.setProperty('--dialog-bg', currentTheme.dialogBg);
root.style.setProperty('--text-color', currentTheme.textColor);
root.style.setProperty('--border-color', currentTheme.borderColor);
root.style.setProperty('--shadow-color', currentTheme.shadowColor);
root.style.setProperty('--button-bg', currentTheme.buttonBg);
root.style.setProperty('--button-hover-bg', currentTheme.buttonHoverBg);
root.style.setProperty('--danger-color', currentTheme.dangerColor);
root.style.setProperty('--success-color', currentTheme.successColor);
root.style.setProperty('--add-color', currentTheme.addColor);
root.style.setProperty('--primary-color', currentTheme.primaryColor);
root.style.setProperty('--info-color', currentTheme.infoColor);
root.style.setProperty('--cancel-color', currentTheme.cancelColor);
root.style.setProperty('--overlay-bg', currentTheme.overlayBg || 'rgba(0, 0, 0, 0.5)');
root.style.setProperty('--tab-bg', currentTheme.tabBg);
root.style.setProperty('--tab-active-bg', currentTheme.tabActiveBg);
root.style.setProperty('--tab-hover-bg', currentTheme.tabHoverBg);
root.style.setProperty('--tab-border', currentTheme.tabBorder);
};

const theme = {
light: {
folderBg: 'rgba(255, 255, 255, 0.8)',
dialogBg: '#ffffff',
textColor: '#333333',
borderColor: '#e5e7eb',
shadowColor: 'rgba(0, 0, 0, 0.1)',
buttonBg: '#f3f4f6',
buttonHoverBg: '#e5e7eb',
dangerColor: '#ef4444',
successColor: '#22c55e',
addColor: '#fd7e14',
primaryColor: '#3B82F6',
infoColor: '#6366F1',
cancelColor: '#6B7280',
overlayBg: 'rgba(0, 0, 0, 0.5)',
tabBg: '#f3f4f6',
tabActiveBg: '#3B82F6',
tabHoverBg: '#e5e7eb',
tabBorder: '#e5e7eb'
},
dark: {
folderBg: 'rgba(31, 41, 55, 0.8)',
dialogBg: '#1f2937',
textColor: '#e5e7eb',
borderColor: '#374151',
shadowColor: 'rgba(0, 0, 0, 0.3)',
buttonBg: '#374151',
buttonHoverBg: '#4b5563',
dangerColor: '#dc2626',
successColor: '#16a34a',
addColor: '#fd7e14',
primaryColor: '#2563EB',
infoColor: '#4F46E5',
cancelColor: '#4B5563',
overlayBg: 'rgba(0, 0, 0, 0.7)',
tabBg: '#374151',
tabActiveBg: '#2563EB',
tabHoverBg: '#4b5563',
tabBorder: '#4b5563'
}
};

const isDarkMode = () => window.matchMedia('(prefers-color-scheme: dark)').matches;
const getCurrentTheme = () => isDarkMode() ? theme.dark : theme.light;

setCSSVariables(getCurrentTheme());

const styles = {
overlay: {
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'var(--overlay-bg, rgba(0, 0, 0, 0.5))',
backdropFilter: 'blur(2px)',
zIndex: 10000,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
transition: 'background-color 0.3s ease, opacity 0.3s ease'
},
dialog: {
position: 'relative',
backgroundColor: 'var(--dialog-bg, #fff
fff)',
color: 'var(--text-color, #333333)',
borderRadius: '4px',
padding: '24px',
boxShadow: '0 8px 24px var(--shadow-color, rgba(0,0,0,0.1))',
border: '1px solid var(--border-color, #e5e7eb)',
transition: 'transform 0.3s ease, opacity 0.3s ease',
maxWidth: '90vw',
maxHeight: '80vh',
overflow: 'auto'
},
button: {
padding: '8px 16px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
transition: 'background-color 0.2s ease, color 0.2s ease',
fontSize: '14px',
fontWeight: '500',
backgroundColor: 'var(--button-bg, #f3f4f6)',
color: 'var(--text-color, #333333)'
}
};

// 默认按钮
const userProvidedButtons = {
"Review": {
type: "template",
text: "You are a code review expert:\n\n{clipboard}\n\nProvide constructive feedback and improvements.\n",
color: "#E6E0FF",
textColor: "#333333",
autoSubmit: false // 新增字段
},
// ... (其他默认按钮保持不变)
"解释": {
type: "template",
text: "Explain the following code concisely:\n\n{clipboard}\n\nFocus on key functionality and purpose.\n",
color: "#ffebcc",
textColor: "#333333",
autoSubmit: false // 新增字段
}
};

// 默认工具按钮
const defaultToolButtons = {
"剪切": {
type: "tool",
action: "cut",
color: "#FFC1CC",
textColor: "#333333"
},
"复制": {
type: "tool",
action: "copy",
color: "#C1FFD7",
textColor: "#333333"
},
"粘贴": {
type: "tool",
action: "paste",
color: "#C1D8FF",
textColor: "#333333"
},
"清空": {
type: "tool",
action: "clear",
color: "#FFD1C1",
textColor: "#333333"
}
};

// 默认配置
const defaultConfig = {
folders: {
"默认": {
color: "#3B82F6",
textColor: "#ffffff",
buttons: userProvidedButtons
},
"🖱️": {
color: "#FFD700", // 金色,可根据需求调整
textColor: "#ffffff",
buttons: defaultToolButtons
}
},
folderOrder: ["默认", "🖱️"],
domainAutoSubmitSettings: [
{
domain: "chatgpt.com",
name: "ChatGPT",
method: "模拟点击提交按钮"
},
{
domain: "chathub.gg",
name: "ChatHub",
method: "Enter"
}
],
/**
* domainStyleSettings: 数组,每个元素结构示例:
* {
* domain: "chatgpt.com",
* name: "ChatGPT自定义样式",
* height: 90,
* cssCode: ".some-class { color: red; }"
* }
*/
domainStyleSettings: []
};

defaultConfig.buttonBarHeight = 40;

let buttonConfig = JSON.parse(localStorage.getItem('chatGPTButtonFoldersConfig')) || JSON.parse(JSON.stringify(defaultConfig));

if (!Array.isArray(buttonConfig.domainStyleSettings)) {
buttonConfig.domainStyleSettings = [];
}

if (typeof buttonConfig.buttonBarHeight !== 'number') {
buttonConfig.buttonBarHeight = defaultConfig.buttonBarHeight;
}

// 若本地无此字段,则初始化
if (!buttonConfig.domainAutoSubmitSettings) {
buttonConfig.domainAutoSubmitSettings = JSON.parse(
JSON.stringify(defaultConfig.domainAutoSubmitSettings)
);
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
}

// 确保所有按钮都有'type'字段
const ensureButtonTypes = () => {
let updated = false;
Object.entries(buttonConfig.folders).forEach(([folderName, folderConfig]) => {
Object.entries(folderConfig.buttons).forEach(([btnName, btnConfig]) => {
if (!btnConfig.type) {
if (folderName === "🖱️") {
btnConfig.type = "tool";
updated = true;
} else {
btnConfig.type = "template";
updated = true;
}
}
// 确保 'autoSubmit' 字段存在,对于模板按钮
if (btnConfig.type === "template" && typeof btnConfig.autoSubmit !== 'boolean') {
btnConfig.autoSubmit = false;
updated = true;
}
});
});
if (updated) {
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
console.log("✅ 已确保所有按钮具有'type'字段和'autoSubmit'配置。");
}
};
ensureButtonTypes();

// 确保工具文件夹存在并包含必要的工具按钮
const ensureToolFolder = () => {
const toolFolderName = "🖱️";
if (!buttonConfig.folders[toolFolderName]) {
buttonConfig.folders[toolFolderName] = {
color: "#FFD700",
textColor: "#ffffff",
buttons: defaultToolButtons
};
buttonConfig.folderOrder.push(toolFolderName);
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
console.log(`✅ 工具文件夹 "${toolFolderName}" 已添加到配置中。`);
} else {
// 确保工具按钮存在
Object.entries(defaultToolButtons).forEach(([btnName, btnCfg]) => {
if (!buttonConfig.folders[toolFolderName].buttons[btnName]) {
buttonConfig.folders[toolFolderName].buttons[btnName] = btnCfg;
console.log(`✅ 工具按钮 "${btnName}" 已添加到文件夹 "${toolFolderName}"。`);
}
});
}
};
ensureToolFolder();

// 变量:防止重复提交
let isSubmitting = false;

const getAllTextareas = (root = document) => {
let textareas = [];
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);
let node = walker.nextNode();
while (node) {
if (node.tagName && (node.tagName.toLowerCase() === 'textarea' || node.getAttribute('contenteditable') === 'true')) {
textareas.push(node);
}
if (node.shadowRoot) {
textareas = textareas.concat(getAllTextareas(node.shadowRoot));
}
node = walker.nextNode();
}
return textareas;
};

/**
* 插入文本到textarea或contenteditable元素中,保持换行符
* @param {HTMLElement} target - 目标元素
* @param {string} finalText - 要插入的文本
* @param {boolean} replaceAll - 是否替换全部内容(用于 {inputBoard} 和 {clipboard} 变量)
*/
const insertTextSmart = (target, finalText, replaceAll = false) => {
const normalizedText = finalText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
if (target.tagName.toLowerCase() === 'textarea') {
if (replaceAll) {
const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
nativeSetter.call(target, normalizedText);
target.selectionStart = target.selectionEnd = normalizedText.length;
const inputEvent = new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertReplacementText',
data: normalizedText,
});
target.dispatchEvent(inputEvent);
} else {
const start = target.selectionStart;
const end = target.selectionEnd;
const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
nativeSetter.call(target, target.value.substring(0, start) + normalizedText + target.value.substring(end));
target.selectionStart = target.selectionEnd = start + normalizedText.length;
const inputEvent = new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: normalizedText,
});
target.dispatchEvent(inputEvent);
}
target.focus();
} else if (target.getAttribute('contenteditable') === 'true') {
if (replaceAll) {
target.innerHTML = '';
const fragment = document.createDocumentFragment();
const lines = finalText.split('\n');
lines.forEach((line, index) => {
if (line === '') {
fragment.appendChild(document.createElement('br'));
} else {
const textNode = document.createTextNode(line);
fragment.appendChild(textNode);
if (index < lines.length - 1) {
fragment.appendChild(document.createElement('br'));
}
}
});
target.appendChild(fragment);
const range = document.createRange();
const sel = window.getSelection();
range.selectNodeContents(target);
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
} else {
const sel = window.getSelection();
if (sel.rangeCount > 0) {
const range = sel.getRangeAt(0);
range.deleteContents();
const fragment = document.createDocumentFragment();
const lines = finalText.split('\n');
lines.forEach((line, index) => {
if (line === '') {
fragment.appendChild(document.createElement('br'));
} else {
fragment.appendChild(document.createTextNode(line));
if (index < lines.length - 1) {
fragment.appendChild(document.createElement('br'));
}
}
});
range.insertNode(fragment);
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
}
}
target.focus();
}
};

/**
* 轮询检测输入框内容是否与预期文本一致。
* @param {HTMLElement} element - 要检测的textarea或contenteditable元素。
* @param {string} expectedText - 期望出现的文本。
* @param {number} interval - 轮询时间间隔(毫秒)。
* @param {number} maxWait - 最大等待时长(毫秒),超时后reject。
* @returns {Promise<void>} - 匹配成功resolve,否则reject。
*/
async function waitForContentMatch(element, expectedText, interval = 100, maxWait = 3000) {
return new Promise((resolve, reject) => {
let elapsed = 0;
const timer = setInterval(() => {
elapsed += interval;
const currentVal = (element.tagName.toLowerCase() === 'textarea')
? element.value
: element.innerText; // contenteditable时用innerText

if (currentVal === expectedText) {
clearInterval(timer);
resolve();
} else if (elapsed >= maxWait) {
clearInterval(timer);
reject(new Error("waitForContentMatch: 超时,输入框内容未能匹配预期文本"));
}
}, interval);
});
}

// 定义等待提交按钮的函数
const waitForSubmitButton = async (maxAttempts = 10, delay = 300) => {
for (let i = 0; i < maxAttempts; i++) {
const submitButton = document.querySelector('button[type="submit"], button[data-testid="send-button"]');
if (submitButton && !submitButton.disabled && submitButton.offsetParent !== null) {
return submitButton;
}
await new Promise(resolve => setTimeout(resolve, delay));
}
return null;
};

// 定义等待时间和尝试次数
const SUBMIT_WAIT_MAX_ATTEMPTS = 10;
const SUBMIT_WAIT_DELAY = 300; // 毫秒

function simulateEnterKey() {
const eventInit = {
bubbles: true,
cancelable: true,
key: "Enter",
code: "Enter",
keyCode: 13,
which: 13
};
const keyboardEvent = new KeyboardEvent('keydown', eventInit);
document.activeElement.dispatchEvent(keyboardEvent);
}

function simulateCmdEnterKey() {
const eventInit = {
bubbles: true,
cancelable: true,
key: "Enter",
code: "Enter",
keyCode: 13,
which: 13,
metaKey: true
};
const keyboardEvent = new KeyboardEvent('keydown', eventInit);
document.activeElement.dispatchEvent(keyboardEvent);
}
// 定义多种提交方式
const submitForm = async () => {
if (isSubmitting) {
console.warn("⚠️ 提交正在进行中,跳过重复提交。");
return false;
}
isSubmitting = true;
try {
const domainRules = buttonConfig.domainAutoSubmitSettings || [];
const currentURL = window.location.href;
const matchedRule = domainRules.find(rule => currentURL.includes(rule.domain));

if (matchedRule) {
console.log("🔎 检测到本域名匹配的自动提交规则:", matchedRule);
switch (matchedRule.method) {
case "Enter":
simulateEnterKey();
isSubmitting = false;
return true;
case "Cmd+Enter":
simulateCmdEnterKey();
isSubmitting = false;
return true;
case "模拟点击提交按钮":
const submitButton = await waitForSubmitButton(SUBMIT_WAIT_MAX_ATTEMPTS, SUBMIT_WAIT_DELAY);
if (submitButton) {
submitButton.click();
console.log("✅ 已根据自动化规则,模拟点击提交按钮。");
isSubmitting = false;
return true;
} else {
console.warn("⚠️ 未找到提交按钮,进入fallback...");
}
break;
default:
console.warn("⚠️ 未知自动提交方式,进入fallback...");
break;
}
}
// 1. 尝试键盘快捷键提交
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const submitKeys = isMac ? ['Enter', 'Meta+Enter'] : ['Enter', 'Control+Enter'];
for (const keyCombo of submitKeys) {
const [key, modifier] = keyCombo.split('+');
const eventInit = {
bubbles: true,
cancelable: true,
key: key,
code: key,
keyCode: key.charCodeAt(0),
which: key.charCodeAt(0),
};
if (modifier === 'Meta') eventInit.metaKey = true;
if (modifier === 'Control') eventInit.ctrlKey = true;
const keyboardEvent = new KeyboardEvent('keydown', eventInit);
document.activeElement.dispatchEvent(keyboardEvent);
console.log(`尝试通过键盘快捷键提交表单:${keyCombo}`);
// 等待短暂时间,查看是否提交成功
await new Promise(resolve => setTimeout(resolve, 500));
// 检查是否页面已提交(可以通过某种标识来确认)
// 这里假设页面会有某种变化,如URL变化或特定元素出现
// 由于具体实现不同,这里仅提供日志
}

// 2. 尝试点击提交按钮
const submitButton = await waitForSubmitButton(SUBMIT_WAIT_MAX_ATTEMPTS, SUBMIT_WAIT_DELAY);
if (submitButton) {
submitButton.click();
console.log("✅ 自动提交已通过点击提交按钮触发。");
return true;
} else {
console.warn("⚠️ 未找到提交按钮,尝试其他提交方式。");
}

// 3. 尝试调用JavaScript提交函数
// 需要知道具体的提交函数名称,这里假设为 `submitForm`
// 根据实际情况调整函数名称
try {
if (typeof submitForm === 'function') {
submitForm();
console.log("✅ 自动提交已通过调用JavaScript函数触发。");
return true;
} else {
console.warn("⚠️ 未找到名为 'submitForm' 的提交函数。");
}
} catch (error) {
console.error("调用JavaScript提交函数失败:", error);
}

// 4. 确保事件监听器触发
// 重新触发 'submit' 事件
try {
const form = document.querySelector('form');
if (form) {
const submitEvent = new Event('submit', {
bubbles: true,
cancelable: true
});
form.dispatchEvent(submitEvent);
console.log("✅ 自动提交已通过触发 'submit' 事件触发。");
return true;
} else {
console.warn("⚠️ 未找到表单元素,无法触发 'submit' 事件。");
}
} catch (error) {
console.error("触发 'submit' 事件失败:", error);
}

console.warn("⚠️ 所有自动提交方式均未成功。");
return false;
} finally {
isSubmitting = false;
}
};

const createCustomButtonElement = (name, config) => {
const button = document.createElement('button');
button.innerText = name;
button.type = 'button';
button.style.backgroundColor = config.color;
button.style.color = config.textColor || '#333333';
button.style.border = '1px solid rgba(0,0,0,0.1)';
button.style.borderRadius = '4px';
button.style.padding = '6px 12px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.transition = 'all 0.2s ease';
button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.05)';
button.style.marginBottom = '6px';
button.style.width = 'fit-content';
button.style.textAlign = 'left';
button.style.display = 'block';
// 鼠标悬停显示按钮模板文本
button.title = config.text || '';
return button;
};

// 引入全局变量来跟踪当前打开的文件夹
const currentlyOpenFolder = {
name: null,
element: null
};

const createCustomButton = (name, config, folderName) => {
const button = createCustomButtonElement(name, config, folderName);
button.setAttribute('draggable', 'true');
button.setAttribute('data-button-name', name);
button.setAttribute('data-folder-name', folderName);

button.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('application/json', JSON.stringify({
buttonName: name,
sourceFolder: folderName,
config: config
}));
e.currentTarget.style.opacity = '0.5';
});

button.addEventListener('dragend', (e) => {
e.currentTarget.style.opacity = '1';
});

button.addEventListener('mousedown', (e) => {
e.preventDefault();
const focusedElement = document.activeElement;
if (focusedElement && (focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
setTimeout(() => focusedElement.focus(), 0);
}
});

button.addEventListener('mouseenter', () => {
button.style.transform = 'scale(1.05)';
button.style.boxShadow = '0 3px 6px rgba(0,0,0,0.1)';
});

button.addEventListener('mouseleave', () => {
button.style.transform = 'scale(1)';
button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.05)';
});

// 处理按钮点击事件
button.addEventListener('click', async (e) => {
e.preventDefault();
if (config.type === "template") {
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}

let clipboardText = '';
try {
clipboardText = await navigator.clipboard.readText();
} catch (err) {
console.error("无法访问剪贴板内容:", err);
alert("无法访问剪贴板内容。请检查浏览器权限。");
return;
}

// 改进的内容获取方式
let inputBoxText = '';
if (focusedElement.tagName.toLowerCase() === 'textarea') {
inputBoxText = focusedElement.value;
} else {
// 遍历 contenteditable 元素的子节点,正确处理换行
const childNodes = Array.from(focusedElement.childNodes);
const textParts = [];
let lastWasBr = false;
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.trim() === '') {
if (!lastWasBr && index > 0) {
textParts.push('\n');
}
} else {
textParts.push(node.textContent);
lastWasBr = false;
}
} else if (node.nodeName === 'BR') {
textParts.push('\n');
lastWasBr = true;
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (node.textContent.trim() === '') {
textParts.push('\n');
} else {
if (textParts.length > 0) {
textParts.push('\n');
}
textParts.push(node.textContent);
}
lastWasBr = false;
}
});
inputBoxText = textParts.join('');
}

const selectionText = window.getSelection().toString();

// 新的变量处理逻辑
let finalText = config.text;
// 存储要替换的内容
const replacements = new Map();
const variableSymbol = Symbol('variable');

// 首先标记所有需要替换的模板变量位置
let counter = 0;
finalText = finalText.replace(/\{\{inputboard\}\|\{clipboard\}}/g, (match) => {
const placeholder = `__TEMPLATE_VAR_${counter++}__`;
replacements.set(placeholder, {
type: variableSymbol,
value: inputBoxText.trim() || clipboardText
});
return placeholder;
});

// 处理单个变量
const singleVarReplacements = {
'{clipboard}': clipboardText,
'{inputboard}': inputBoxText,
'{selection}': selectionText
};

// 替换单个变量
Object.entries(singleVarReplacements).forEach(([pattern, value]) => {
const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
finalText = finalText.replace(new RegExp(escapedPattern, 'g'), (match) => {
const placeholder = `__TEMPLATE_VAR_${counter++}__`;
replacements.set(placeholder, {
type: variableSymbol,
value: value
});
return placeholder;
});
});

// 最后进行实际替换
replacements.forEach((data, placeholder) => {
if (data.type === variableSymbol) {
finalText = finalText.replace(new RegExp(placeholder, 'g'), data.value);
}
});

// 统一换行符
finalText = finalText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');

const containsInputboard = config.text.includes("{inputboard}") ||
config.text.includes("{{inputboard}|{clipboard}}");

if (containsInputboard) {
insertTextSmart(focusedElement, finalText, true);
console.log(`✅ 使用 {inputboard} 变量,输入框内容已被替换。`);
} else {
insertTextSmart(focusedElement, finalText, false);
console.log(`✅ 插入了预设文本。`);
}

// 若开启autoSubmit,则先检测是否完成替换,再延时后提交
if (config.autoSubmit) {
try {
// 1. 等待输入框内容与 finalText 匹配,最多等待3秒
await waitForContentMatch(focusedElement, finalText, 100, 3000);

// 2. 再额外等待500ms,确保渲染/加载稳定
await new Promise(resolve => setTimeout(resolve, 500));

// 3. 调用自动提交
const success = await submitForm();
if (success) {
console.log("✅ 自动提交成功(已确认内容替换完成)。");
} else {
console.warn("⚠️ 自动提交失败。");
}
} catch (error) {
console.error("自动提交前检测文本匹配超时或错误:", error);
}
}

}else if (config.type === "tool") {
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
switch (config.action) {
case "cut":
handleCut(focusedElement);
break;
case "copy":
handleCopy(focusedElement);
break;
case "paste":
handlePaste(focusedElement);
break;
case "clear":
handleClear(focusedElement);
break;
default:
console.warn(`未知的工具按钮动作: ${config.action}`);
}
}

// 立即关闭弹窗
if (currentlyOpenFolder.name === folderName && currentlyOpenFolder.element) {
currentlyOpenFolder.element.style.display = 'none';
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`✅ 弹窗 "${folderName}" 已立即关闭。`);
} else {
console.warn(`⚠️ 弹窗 "${folderName}" 未被识别为当前打开的弹窗。`);
}

});

return button;
};

// 工具按钮动作处理
const handleCut = (element) => {
let text = '';
if (element.tagName.toLowerCase() === 'textarea') {
text = element.value;
insertTextSmart(element, '', true);
} else {
const textContent = [];
const childNodes = Array.from(element.childNodes);
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
textContent.push(node.textContent);
} else if (node.nodeName === 'BR') {
textContent.push('\n');
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (index > -1) textContent.push('\n');
textContent.push(node.textContent);
}
});
text = textContent.join('');
insertTextSmart(element, '', true);
}
if (text) {
navigator.clipboard.writeText(text).then(() => {
console.log("✅ 已剪切输入框内容到剪贴板。");
showTemporaryFeedback(element, '剪切成功');
}).catch(err => {
console.error("剪切失败:", err);
alert("剪切失败,请检查浏览器权限。");
});
}
};

const handleCopy = (element) => {
let text = '';
if (element.tagName.toLowerCase() === 'textarea') {
text = element.value;
} else {
const textContent = [];
const childNodes = Array.from(element.childNodes);
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
textContent.push(node.textContent);
} else if (node.nodeName === 'BR') {
textContent.push('\n');
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (index > -1) textContent.push('\n');
textContent.push(node.textContent);
}
});
text = textContent.join('');
}
if (text) {
navigator.clipboard.writeText(text).then(() => {
console.log("✅ 已复制输入框内容到剪贴板。");
showTemporaryFeedback(element, '复制成功');
}).catch(err => {
console.error("复制失败:", err);
alert("复制失败,请检查浏览器权限。");
});
}
};

const handlePaste = async (element) => {
try {
const clipboardText = await navigator.clipboard.readText();
insertTextSmart(element, clipboardText);
console.log("✅ 已粘贴剪贴板内容到输入框。");
showTemporaryFeedback(element, '粘贴成功');
} catch (err) {
console.error("粘贴失败:", err);
alert("粘贴失败,请检查浏览器权限。");
}
};

const handleClear = (element) => {
insertTextSmart(element, '', true);
console.log("✅ 输入框内容已清空。");
showTemporaryFeedback(element, '清空成功');
};

const showTemporaryFeedback = (element, message) => {
const feedback = document.createElement('span');
feedback.textContent = message;
feedback.style.position = 'absolute';
feedback.style.bottom = '10px';
feedback.style.right = '10px';
feedback.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
feedback.style.color = '#fff';
feedback.style.padding = '4px 8px';
feedback.style.borderRadius = '4px';
feedback.style.zIndex = '10001';
element.parentElement.appendChild(feedback);
setTimeout(() => {
feedback.remove();
}, 1500);
};

const createFolderButton = (folderName, folderConfig) => {
const folderButton = document.createElement('button');
folderButton.innerText = folderName;
folderButton.type = 'button';
folderButton.style.backgroundColor = folderConfig.color;
folderButton.style.color = folderConfig.textColor || '#ffffff';
folderButton.style.border = 'none';
folderButton.style.borderRadius = '4px';
folderButton.style.padding = '6px 12px';
folderButton.style.cursor = 'pointer';
folderButton.style.fontSize = '14px';
folderButton.style.fontWeight = '500';
folderButton.style.transition = 'all 0.2s ease';
folderButton.style.position = 'relative';
folderButton.style.display = 'inline-flex';
folderButton.style.alignItems = 'center';
folderButton.style.whiteSpace = 'nowrap';
folderButton.style.zIndex = '99';
folderButton.classList.add('folder-button');
folderButton.setAttribute('data-folder', folderName);

folderButton.addEventListener('mousedown', (e) => {
e.preventDefault();
});

folderButton.addEventListener('mouseleave', () => {
folderButton.style.transform = 'scale(1)';
folderButton.style.boxShadow = 'none';
});

const buttonListContainer = document.createElement('div');
buttonListContainer.style.position = 'fixed';
buttonListContainer.style.display = 'none';
buttonListContainer.style.flexDirection = 'column';
buttonListContainer.style.backgroundColor = 'var(--folder-bg, rgba(255, 255, 255, 0.8))';
buttonListContainer.style.backdropFilter = 'blur(5px)';
buttonListContainer.style.border = `1px solid var(--border-color, #e5e7eb)`;
buttonListContainer.style.borderRadius = '8px';
buttonListContainer.style.padding = '10px';
buttonListContainer.style.paddingBottom = '2.5px';
buttonListContainer.style.boxShadow = `0 4px 12px var(--shadow-color, rgba(0,0,0,0.1))`;
buttonListContainer.style.zIndex = '100';
buttonListContainer.style.maxHeight = '800px';
buttonListContainer.style.overflowY = 'auto';
buttonListContainer.style.transition = 'all 0.3s ease';
buttonListContainer.classList.add('button-list');
buttonListContainer.setAttribute('data-folder-list', folderName);

Object.entries(folderConfig.buttons).forEach(([name, config]) => {
const customButton = createCustomButton(name, config, folderName);
buttonListContainer.appendChild(customButton);
});

folderButton.addEventListener('click', (e) => {
e.preventDefault();
// Toggle popup visibility
if (currentlyOpenFolder.name === folderName) {
// 如果当前文件夹已经打开,则关闭它
buttonListContainer.style.display = 'none';
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`🔒 弹窗 "${folderName}" 已关闭。`);
} else {
// 关闭其他文件夹的弹窗
if (currentlyOpenFolder.element) {
currentlyOpenFolder.element.style.display = 'none';
console.log(`🔒 弹窗 "${currentlyOpenFolder.name}" 已关闭。`);
}
// 打开当前文件夹的弹窗
buttonListContainer.style.display = 'flex';
currentlyOpenFolder.name = folderName;
currentlyOpenFolder.element = buttonListContainer;
console.log(`🔓 弹窗 "${folderName}" 已打开。`);
// 动态定位弹窗位置
const rect = folderButton.getBoundingClientRect();
buttonListContainer.style.bottom = `40px`;
buttonListContainer.style.left = `${rect.left + window.scrollX - 20}px`;
console.log(`📍 弹窗位置设置为 Bottom: 40px, Left: ${rect.left + window.scrollX - 20}px`);
}
});

document.addEventListener('click', (e) => {
if (!folderButton.contains(e.target) && !buttonListContainer.contains(e.target)) {
// 点击了其他地方,关闭弹窗
if (buttonListContainer.style.display !== 'none') {
buttonListContainer.style.display = 'none';
if (currentlyOpenFolder.name === folderName) {
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`🔒 弹窗 "${folderName}" 已关闭(点击外部区域)。`);
}
}
}
});

// 将buttonListContainer移动到body以避免事件传播干扰
document.body.appendChild(buttonListContainer);
return folderButton;
};

const toggleFolder = (folderName, state) => {
const buttonList = document.querySelector(`.button-list[data-folder-list="${folderName}"]`);
if (!buttonList) {
console.warn(`⚠️ 未找到与文件夹 "${folderName}" 关联的弹窗。`);
return;
}
if (state) {
// 打开当前文件夹的弹窗
buttonList.style.display = 'flex';
currentlyOpenFolder.name = folderName;
currentlyOpenFolder.element = buttonList;
console.log(`🔓 弹窗 "${folderName}" 已打开(toggleFolder)。`);
} else {
// 关闭当前文件夹的弹窗
buttonList.style.display = 'none';
if (currentlyOpenFolder.name === folderName) {
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`🔒 弹窗 "${folderName}" 已关闭(toggleFolder)。`);
}
}
// 关闭其他文件夹的弹窗
const allButtonLists = document.querySelectorAll('.button-list');
allButtonLists.forEach(bl => {
if (bl.getAttribute('data-folder-list') !== folderName) {
bl.style.display = 'none';
const fname = bl.getAttribute('data-folder-list');
if (currentlyOpenFolder.name === fname) {
currentlyOpenFolder.name = null;
currentlyOpenFolder.element = null;
console.log(`🔒 弹窗 "${fname}" 已关闭(toggleFolder 关闭其他弹窗)。`);
}
}
});
};

const closeExistingOverlay = (overlay) => {
if (overlay && overlay.parentElement) {
overlay.parentElement.removeChild(overlay);
}
};

let currentConfirmOverlay = null;
let currentSettingsOverlay = null;
let currentConfigOverlay = null; // 新增的独立配置设置弹窗

const showDeleteFolderConfirmDialog = (folderName, rerenderFn) => {
if (currentConfirmOverlay) {
closeExistingOverlay(currentConfirmOverlay);
}
const folderConfig = buttonConfig.folders[folderName];
if (!folderConfig) {
alert(`文件夹 "${folderName}" 不存在。`);
return;
}
// 构建文件夹内自定义按钮的垂直预览列表
let buttonsPreviewHTML = '';
Object.entries(folderConfig.buttons).forEach(([btnName, btnCfg]) => {
buttonsPreviewHTML += `
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<button style="
background-color: ${btnCfg.color};
color: ${btnCfg.textColor};
border: 1px solid rgba(0,0,0,0.1);
border-radius: 4px;
padding: 4px 8px;
cursor: default;
font-size: 12px;
box-shadow: none;
margin-right: 8px;
" disabled>${btnName}</button>
<span style="font-size: 12px; color: var(--text-color);">${btnName}</span>
</div>
`;
});

const overlay = document.createElement('div');
overlay.classList.add('confirm-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('confirm-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 400px;
max-width: 90vw;
`;
dialog.innerHTML = `
<h3 style="margin: 0 0 20px 0; font-size: 18px; font-weight: 600; color: var(--danger-color, #ef4444);">
🗑️ 确认删除文件夹 "${folderName}"?
</h3>
<p style="margin: 8px 0; color: var(--text-color, #333333);">❗️ 注意:此操作无法撤销!<br/>(删除文件夹将同时删除其中的所有自定义按钮!)</p>
<div style="margin: 16px 0; border: 1px solid var(--border-color, #e5e7eb); padding: 8px; border-radius:4px;">
<!-- 将文件夹按钮预览和文字标签放在一行 -->
<p style="margin:4px 0; display: flex; align-items: center; gap: 8px; color: var(--text-color, #333333);">
<strong>1️⃣ 文件夹按钮外观:</strong>
<button style="
background-color: ${folderConfig.color};
color: ${folderConfig.textColor};
border: none;
border-radius:4px;
padding:6px 12px;
cursor: default;
font-size:14px;
font-weight:500;
box-shadow: none;
" disabled>${folderName}</button>
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
按钮名称: ${folderName}
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
背景颜色: <span style="display:inline-block;width:16px;height:16px;background:${folderConfig.color};border:1px solid #333;vertical-align:middle;margin-right:4px;"></span>${folderConfig.color}
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
文字颜色: <span style="display:inline-block;width:16px;height:16px;background:${folderConfig.textColor};border:1px solid #333;vertical-align:middle;margin-right:4px;"></span>${folderConfig.textColor}
</p>
<hr style="margin: 8px 0; border: none; border-top: 1px solid var(--border-color, #e5e7eb);">
<p style="margin:4px 0; color: var(--text-color, #333333);"><strong>2️⃣ 文件夹内,全部自定义按钮:</strong></p>
<div style="display: flex; flex-direction: column; gap: 8px;">
${buttonsPreviewHTML}
</div>
</div>
<div style="
display: flex;
justify-content: flex-end;
gap: 12px;
border-top:1px solid var(--border-color, #e5e7eb);
padding-top:16px;
">
<button id="cancelDeleteFolder" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius: 4px;
">取消</button>
<button id="confirmDeleteFolder" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--danger-color, #ef4444);
color: white;
border-radius: 4px;
">删除</button>
</div>
`;
overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfirmOverlay = overlay;

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

dialog.querySelector('#cancelDeleteFolder').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
});

dialog.querySelector('#confirmDeleteFolder').addEventListener('click', () => {
delete buttonConfig.folders[folderName];
const idx = buttonConfig.folderOrder.indexOf(folderName);
if (idx > -1) buttonConfig.folderOrder.splice(idx, 1);
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
if (rerenderFn) rerenderFn();
console.log(`🗑️ 文件夹 "${folderName}" 已删除。`);
// 更新按钮栏
updateButtonContainer();
});
};

// 修改 删除按钮确认对话框,增加显示按钮名称、颜色信息及样式预览
const showDeleteButtonConfirmDialog = (folderName, btnName, rerenderFn) => {
if (currentConfirmOverlay) {
closeExistingOverlay(currentConfirmOverlay);
}
const btnCfg = buttonConfig.folders[folderName].buttons[btnName];
if (!btnCfg) {
alert(`按钮 "${btnName}" 不存在于文件夹 "${folderName}" 中。`);
return;
}
const overlay = document.createElement('div');
overlay.classList.add('confirm-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('confirm-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 400px;
max-width: 90vw;
`;
dialog.innerHTML = `
<h3 style="margin: 0 0 20px 0; font-size: 18px; font-weight: 600; color: var(--danger-color, #ef4444);">
🗑️ 确认删除按钮 "${btnName}"?
</h3>
<p style="margin: 8px 0; color: var(--text-color, #333333);">❗️ 注意:此操作无法撤销!</p>
<div style="margin: 16px 0; border: 1px solid var(--border-color, #e5e7eb); padding: 8px; border-radius:4px;">
<p style="margin:4px 0; display: flex; align-items: center; gap: 8px; color: var(--text-color, #333333);">
<strong>1️⃣ 自定义按钮外观:</strong>
<button style="
background-color: ${btnCfg.color};
color: ${btnCfg.textColor};
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: default;
font-size: 12px;
box-shadow: none;
" disabled>${btnName}</button>
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
按钮名称: ${btnName}
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
按钮背景颜色: <span style="display:inline-block;width:16px;height:16px;background:${btnCfg.color};border:1px solid #333;vertical-align:middle;margin-right:4px;"></span>${btnCfg.color}
</p>
<p style="margin:4px 0; position:relative; padding-left:12px; color: var(--text-color, #333333);">
<span style="position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:4px; background-color: var(--text-color, #333333); border-radius:50%;"></span>
按钮文字颜色: <span style="display:inline-block;width:16px;height:16px;background:${btnCfg.textColor};border:1px solid #333;vertical-align:middle;margin-right:4px;"></span>${btnCfg.textColor}
</p>
<hr style="margin: 8px 0; border: none; border-top: 1px solid var(--border-color, #e5e7eb);">
<p style="margin:4px 0; color: var(--text-color, #333333);"><strong>2️⃣ 按钮对应的文本模板:</strong></p>
<textarea readonly style="
width:100%;
height:150px;
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
border:1px solid var(--border-color, #e5e7eb);
border-radius:4px;
resize: vertical;
">${btnCfg.text || ''}</textarea>
</div>
<div style="
display:flex;
justify-content: flex-end;
gap: 12px;
border-top:1px solid var(--border-color, #e5e7eb);
padding-top:16px;
">
<button id="cancelDeleteButton" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius:4px;
">取消</button>
<button id="confirmDeleteButton" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--danger-color, #ef4444);
color: white;
border-radius:4px;
">删除</button>
</div>
`;
overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfirmOverlay = overlay;

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

dialog.querySelector('#cancelDeleteButton').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
});

dialog.querySelector('#confirmDeleteButton').addEventListener('click', () => {
delete buttonConfig.folders[folderName].buttons[btnName];
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
if (rerenderFn) rerenderFn();
console.log(`🗑️ 按钮 "${btnName}" 已删除。`);
// 更新按钮栏
updateButtonContainer();
});
};

const showButtonEditDialog = (folderName, btnName = '', btnConfig = {}, rerenderFn) => {
if (currentConfirmOverlay) {
closeExistingOverlay(currentConfirmOverlay);
}
// 禁止编辑/删除工具文件夹中的工具按钮
if (folderName === "🖱️" && btnConfig.type === "tool") {
alert('工具文件夹中的工具按钮无法编辑或删除。');
return;
}
const isEdit = btnName !== '';

// Create overlay and dialog containers
const overlay = document.createElement('div');
overlay.classList.add('edit-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('edit-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 500px;
max-width: 90vw;
`;

const initialName = btnName || '';
const initialColor = btnConfig.color || '#FFC1CC';
const initialTextColor = btnConfig.textColor || '#333333';
const initialAutoSubmit = btnConfig.autoSubmit || false; // 新增字段

// 预览部分
const previewSection = `
<div style="
margin: -24px -24px 20px -24px;
padding: 16px 24px;
background-color: var(--button-bg, #f3f4f6);
border-bottom: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px 4px 0 0;
display: flex;
align-items: center;
gap: 16px;
">
<div style="
font-size: 18px;
font-weight: 600;
color: var(--text-color, #333333);
display: flex;
align-items: center;
gap: 8px;
">
${isEdit ? '✏️ 编辑按钮:' : '🆕 新建按钮:'}
</div>
<div id="buttonPreview" style="
display: inline-flex;
padding: 4px;
border-radius: 4px;
background-color: var(--dialog-bg, #ffffff);
">
<button id="previewButton" style="
background-color: ${initialColor};
color: ${initialTextColor};
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: default;
font-size: 14px;
transition: all 0.2s ease;
">${initialName || '预览按钮'}</button>
</div>
</div>
`;

// Tab content for text template
const textTemplateTab = `
<div id="textTemplateTab" class="tab-content" style="display: block;">
<div style="
width: 100%;
padding: 12px;
border-radius: 4px;
border: 1px solid var(--border-color, #e5e7eb);
background-color: var(--button-bg, #f3f4f6);
">
<div style="
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
">
<label style="
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
white-space: nowrap;
">插入变量:</label>
<div id="quickInsertButtons" style="
display: flex;
gap: 8px;
flex-wrap: wrap;
">
<button type="button" data-insert="{inputBoard}" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px;
font-size: 12px;
padding: 4px 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
">📝 输入框</button>
<button type="button" data-insert="{clipboard}" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px;
font-size: 12px;
padding: 4px 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
">📋 剪贴板</button>
<button type="button" data-insert="{selection}" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px;
font-size: 12px;
padding: 4px 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
">🔍 选中</button>
<button type="button" data-insert="{{inputBoard}|{clipboard}}" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px;
font-size: 12px;
padding: 4px 8px;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
">🔄 输入框/剪贴板</button>
</div>
</div>
<textarea id="buttonText" style="
width: 100%;
height: 150px;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color, #e5e7eb);
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
resize: vertical;
">${btnConfig.text || ''}</textarea>
</div>
</div>`;

// Tab content for style settings
const styleSettingsTab = `
<div id="styleSettingsTab" class="tab-content" style="display: none;">
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: var(--text-color, #333333);">按钮名称:</label>
<input type="text" id="buttonName" value="${btnName}" style="
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color, #e5e7eb);
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
">
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: var(--text-color, #333333);">按钮背景颜色:</label>
<input type="color" id="buttonColor" value="${btnConfig.color || '#FFC1CC'}" style="
width: 100px;
height: 40px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
cursor: pointer;
background-color: var(--button-bg, #f3f4f6);
">
</div>
<div style="margin-bottom: 0px;">
<label style="display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: var(--text-color, #333333);">按钮文字颜色:</label>
<input type="color" id="buttonTextColor" value="${btnConfig.textColor || '#333333'}" style="
width: 100px;
height: 40px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
cursor: pointer;
background-color: var(--button-bg, #f3f4f6);
">
</div>
</div>
`;

// 新增的提交设置子标签页
const submitSettingsTab = `
<div id="submitSettingsTab" class="tab-content" style="display: none;">
<div style="margin-bottom: 20px;">
<label style="
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
cursor: pointer;
gap: 6px;
">
<input type="checkbox" id="autoSubmitCheckbox" style="cursor: pointer;" ${initialAutoSubmit ? 'checked' : ''}>
自动提交 (在填充后自动提交内容)
</label>
</div>
</div>
`;

// Tab navigation
const tabNavigation = `
<div style="
display: flex;
gap: 10px;
margin-bottom: 20px;
border-bottom: 1px solid var(--border-color, #e5e7eb);
">
<button class="tab-button active" data-tab="textTemplateTab" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--primary-color, #3B82F6);
color: white;
border-radius: 4px 4px 0 0;
border-bottom: 2px solid transparent;
">文本模板</button>
<button class="tab-button" data-tab="styleSettingsTab" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
border-radius: 4px 4px 0 0;
border-bottom: 2px solid transparent;
">样式设置</button>
<button class="tab-button" data-tab="submitSettingsTab" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
border-radius: 4px 4px 0 0;
border-bottom: 2px solid transparent;
">提交设置</button>
</div>
`;

// Footer buttons
const footerButtons = `
<div style="
display: flex;
justify-content: space-between;
margin-top: 30px;
padding-top: 20px;
border-top:1px solid var(--border-color, #e5e7eb);
">
<button id="cancelButtonEdit" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius: 4px;
">取消</button>
<button id="saveButtonEdit" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--success-color, #22c55e);
color: white;
border-radius: 4px;
">确认</button>
</div>
`;

// Combine all sections
dialog.innerHTML = `
${previewSection}
${tabNavigation}
${textTemplateTab}
${styleSettingsTab}
${submitSettingsTab}
${footerButtons}
`;

// Add tab switching functionality
const setupTabs = () => {
const tabButtons = dialog.querySelectorAll('.tab-button');
const tabContents = dialog.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const tabId = button.dataset.tab;
// Update button styles
tabButtons.forEach(btn => {
if (btn === button) {
btn.style.backgroundColor = 'var(--primary-color, #3B82F6)';
btn.style.color = 'white';
btn.style.borderBottom = '2px solid var(--primary-color, #3B82F6)';
} else {
btn.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
btn.style.color = 'var(--text-color, #333333)';
btn.style.borderBottom = '2px solid transparent';
}
});
// Show/hide content
tabContents.forEach(content => {
content.style.display = content.id === tabId ? 'block' : 'none';
});
});
});
};

// Rest of the existing dialog setup code...
overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfirmOverlay = overlay;

// Setup tabs
setupTabs();

// Setup preview updates
const setupPreviewUpdates = () => {
const previewButton = dialog.querySelector('#previewButton');
const buttonNameInput = dialog.querySelector('#buttonName');
const buttonColorInput = dialog.querySelector('#buttonColor');
const buttonTextColorInput = dialog.querySelector('#buttonTextColor');
const autoSubmitCheckbox = dialog.querySelector('#autoSubmitCheckbox'); // 新增引用

buttonNameInput?.addEventListener('input', (e) => {
previewButton.textContent = e.target.value || '预览按钮';
});

buttonColorInput?.addEventListener('input', (e) => {
previewButton.style.backgroundColor = e.target.value;
});

buttonTextColorInput?.addEventListener('input', (e) => {
previewButton.style.color = e.target.value;
});

// 监听“自动提交”开关变化
autoSubmitCheckbox?.addEventListener('change', (e) => {
console.log(`✅ 自动提交开关已设置为 ${e.target.checked}`);
});
};
setupPreviewUpdates();

// Setup quick insert buttons
const setupQuickInsert = () => {
const buttonText = dialog.querySelector('#buttonText');
const quickInsertButtons = dialog.querySelector('#quickInsertButtons');
quickInsertButtons?.addEventListener('click', (e) => {
const button = e.target.closest('button[data-insert]');
if (!button) return;
e.preventDefault();
const insertText = button.dataset.insert;
const start = buttonText.selectionStart;
const end = buttonText.selectionEnd;
buttonText.value = buttonText.value.substring(0, start) +
insertText +
buttonText.value.substring(end);
buttonText.selectionStart = buttonText.selectionEnd = start + insertText.length;
buttonText.focus();
});
quickInsertButtons?.addEventListener('mousedown', (e) => {
if (e.target.closest('button[data-insert]')) {
e.preventDefault();
}
});
};
setupQuickInsert();

// Animation effect
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

// Setup buttons
dialog.querySelector('#cancelButtonEdit')?.addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
});

dialog.querySelector('#saveButtonEdit')?.addEventListener('click', () => {
const newBtnName = dialog.querySelector('#buttonName').value.trim();
const newBtnColor = dialog.querySelector('#buttonColor').value;
const newBtnTextColor = dialog.querySelector('#buttonTextColor').value;
const newBtnText = dialog.querySelector('#buttonText').value.trim();
const autoSubmit = dialog.querySelector('#autoSubmitCheckbox')?.checked || false; // 获取自动提交状态

if (!newBtnName) {
alert('请输入按钮名称!');
return;
}

if (!isValidColor(newBtnColor) || !isValidColor(newBtnTextColor)) {
alert('请选择有效的颜色!');
return;
}

if (newBtnName !== btnName && buttonConfig.folders[folderName].buttons[newBtnName]) {
alert('按钮名称已存在!');
return;
}

// Get all buttons order
const currentButtons = { ...buttonConfig.folders[folderName].buttons };

if (btnConfig.type === "tool") {
// 工具按钮不允许更改类型和动作
buttonConfig.folders[folderName].buttons[newBtnName] = {
type: "tool",
action: btnConfig.action,
color: newBtnColor,
textColor: newBtnTextColor
};
} else {
// 处理模板按钮
// Handle button rename
if (btnName && newBtnName !== btnName) {
const newButtons = {};
Object.keys(currentButtons).forEach(key => {
if (key === btnName) {
newButtons[newBtnName] = {
text: newBtnText,
color: newBtnColor,
textColor: newBtnTextColor,
type: "template",
autoSubmit: autoSubmit
};
} else {
newButtons[key] = currentButtons[key];
}
});
buttonConfig.folders[folderName].buttons = newButtons;
} else {
// Update existing button
if (btnName) {
buttonConfig.folders[folderName].buttons[btnName] = {
text: newBtnText,
color: newBtnColor,
textColor: newBtnTextColor,
type: "template",
autoSubmit: autoSubmit
};
} else {
// Create new button
buttonConfig.folders[folderName].buttons[newBtnName] = {
text: newBtnText,
color: newBtnColor,
textColor: newBtnTextColor,
type: "template",
autoSubmit: autoSubmit
};
}
}
}

localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
if (rerenderFn) rerenderFn();
console.log(`✅ 按钮 "${newBtnName}" 已保存。`);
updateButtonContainer();
});
};

function isValidColor(color) {
const s = new Option().style;
s.color = color;
return s.color !== '';
}

const showFolderEditDialog = (folderName = '', folderConfig = {}, rerenderFn) => {
if (currentConfirmOverlay) {
closeExistingOverlay(currentConfirmOverlay);
}
const isNew = !folderName;
const overlay = document.createElement('div');
overlay.classList.add('folder-edit-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('folder-edit-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 500px;
max-width: 90vw;
`;

const initialName = folderName || '';
const initialColor = folderConfig.color || '#3B82F6';
const initialTextColor = folderConfig.textColor || '#ffffff';

// 预览部分
const previewSection = `
<div style="
margin: -24px -24px 20px -24px;
padding: 16px 24px;
background-color: var(--button-bg, #f3f4f6);
border-bottom: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px 4px 0 0;
display: flex;
align-items: center;
gap: 16px;
">
<div style="
font-size: 18px;
font-weight: 600;
color: var(--text-color, #333333);
display: flex;
align-items: center;
gap: 8px;
">
${isNew ? '🆕 新建文件夹:' : '✏️ 编辑文件夹:'}
</div>
<div id="folderPreview" style="
display: inline-flex;
padding: 4px;
border-radius: 4px;
background-color: var(--dialog-bg, #ffffff);
">
<button id="previewButton" style="
background-color: ${initialColor};
color: ${initialTextColor};
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: default;
font-size: 14px;
transition: all 0.2s ease;
">${initialName || '预览文件夹'}</button>
</div>
</div>
`;

// 设置部分
const settingsSection = `
<div style="
display:flex;
flex-direction:column;
gap:20px;
margin-bottom:20px;
">
<div style="margin-bottom: 20px;">
<label style="
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
">文件夹名称:</label>
<input type="text" id="folderNameInput" value="${initialName}" style="
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid var(--border-color, #e5e7eb);
background-color: var(--button-bg, #f3f4f6);
color: var(--text-color, #333333);
">
</div>
<div style="margin-bottom: 20px;">
<label style="
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
">按钮背景颜色:</label>
<input type="color" id="folderColorInput" value="${initialColor}" style="
width: 100px;
height: 40px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
cursor: pointer;
background-color: var(--button-bg, #f3f4f6);
">
</div>
<div style="margin-bottom: 0px;">
<label style="
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-color, #333333);
">按钮文字颜色:</label>
<input type="color" id="folderTextColorInput" value="${initialTextColor}" style="
width: 100px;
height: 40px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
cursor: pointer;
background-color: var(--button-bg, #f3f4f6);
">
</div>
</div>
`;

// 底部按钮
const footerButtons = `
<div style="
display: flex;
justify-content: space-between;
margin-top: 30px;
padding-top: 20px;
border-top:1px solid var(--border-color, #e5e7eb);
">
<button id="cancelFolderEdit" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius: 4px;
">取消</button>
<button id="saveFolderEdit" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--success-color, #22c55e);
color: white;
border-radius: 4px;
">确认</button>
</div>
`;

// Combine all sections
dialog.innerHTML = `
${previewSection}
${settingsSection}
${footerButtons}
`;

// 添加事件监听器
const setupPreviewUpdates = () => {
const previewButton = dialog.querySelector('#previewButton');
const folderNameInput = dialog.querySelector('#folderNameInput');
const folderColorInput = dialog.querySelector('#folderColorInput');
const folderTextColorInput = dialog.querySelector('#folderTextColorInput');

folderNameInput?.addEventListener('input', (e) => {
previewButton.textContent = e.target.value || '预览文件夹';
});

folderColorInput?.addEventListener('input', (e) => {
previewButton.style.backgroundColor = e.target.value;
});

folderTextColorInput?.addEventListener('input', (e) => {
previewButton.style.color = e.target.value;
});
};
setupPreviewUpdates();

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfirmOverlay = overlay;

// Animation effect
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

// Setup buttons
dialog.querySelector('#cancelFolderEdit').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
});

dialog.querySelector('#saveFolderEdit').addEventListener('click', () => {
const newFolderName = dialog.querySelector('#folderNameInput').value.trim();
const newColor = dialog.querySelector('#folderColorInput').value;
const newTextColor = dialog.querySelector('#folderTextColorInput').value;

if (!newFolderName) {
alert('请输入文件夹名称');
return;
}

if (isNew && buttonConfig.folders[newFolderName]) {
alert("该文件夹已存在!");
return;
}

if (!isNew && newFolderName !== folderName && buttonConfig.folders[newFolderName]) {
alert("该文件夹已存在!");
return;
}

if (!isNew && newFolderName !== folderName) {
const oldButtons = buttonConfig.folders[folderName].buttons;
buttonConfig.folders[newFolderName] = {
...buttonConfig.folders[folderName],
color: newColor,
textColor: newTextColor,
buttons: { ...oldButtons }
};
delete buttonConfig.folders[folderName];
const idx = buttonConfig.folderOrder.indexOf(folderName);
if (idx > -1) {
buttonConfig.folderOrder[idx] = newFolderName;
}
} else {
buttonConfig.folders[newFolderName] = buttonConfig.folders[newFolderName] || { buttons: {} };
buttonConfig.folders[newFolderName].color = newColor;
buttonConfig.folders[newFolderName].textColor = newTextColor;
}

// 确保所有按钮都有'type'字段和'autoSubmit'字段
Object.entries(buttonConfig.folders).forEach(([folderName, folderCfg]) => {
Object.entries(folderCfg.buttons).forEach(([btnName, btnCfg]) => {
if (!btnCfg.type) {
if (folderName === "🖱️") {
btnCfg.type = "tool";
} else {
btnCfg.type = "template";
}
}
if (btnCfg.type === "template" && typeof btnCfg.autoSubmit !== 'boolean') {
btnCfg.autoSubmit = false;
}
});
});

localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
if (rerenderFn) rerenderFn(newFolderName);
console.log(`✅ 文件夹 "${newFolderName}" 已保存。`);
updateButtonContainer();
});
};

const createSettingsButton = () => {
const button = document.createElement('button');
button.innerText = '⚙️';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.addEventListener('click', showUnifiedSettingsDialog);
return button;
};

const createCutButton = () => {
const button = document.createElement('button');
button.innerText = '✂️';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.title = '剪切输入框内容';
// 阻止mousedown默认行为以维持输入框焦点
button.addEventListener('mousedown', (e) => {
e.preventDefault();
});
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
let text = '';
if (focusedElement.tagName.toLowerCase() === 'textarea') {
text = focusedElement.value;
// 清空textarea内容
const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
nativeSetter.call(focusedElement, '');
const inputEvent = new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'deleteContent'
});
focusedElement.dispatchEvent(inputEvent);
} else {
// 处理contenteditable元素
const childNodes = Array.from(focusedElement.childNodes);
const textParts = [];
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
textParts.push(node.textContent);
} else if (node.nodeName === 'BR') {
textParts.push('\n');
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (index > -1) textParts.push('\n');
textParts.push(node.textContent);
}
});
text = textParts.join('');
// 清空contenteditable内容
focusedElement.innerHTML = '';
}
if (text) {
navigator.clipboard.writeText(text).then(() => {
console.log("✅ 已剪切输入框内容到剪贴板。");
showTemporaryFeedback(focusedElement, '剪切成功');
}).catch(err => {
console.error("剪切失败:", err);
alert("剪切失败,请检查浏览器权限。");
});
}
// 确保输入框保持焦点
focusedElement.focus();
// 如果是textarea,还需要设置光标位置到开始处
if (focusedElement.tagName.toLowerCase() === 'textarea') {
focusedElement.selectionStart = focusedElement.selectionEnd = 0;
}
console.log("✅ 输入框内容已清空。");
showTemporaryFeedback(focusedElement, '清空成功');
});
return button;
};

const createCopyButton = () => {
const button = document.createElement('button');
button.innerText = '🅲';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.title = '复制输入框内容';
// 阻止mousedown默认行为以维持输入框焦点
button.addEventListener('mousedown', (e) => {
e.preventDefault();
});
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
let text = '';
if (focusedElement.tagName.toLowerCase() === 'textarea') {
text = focusedElement.value;
} else {
const textContent = [];
const childNodes = Array.from(focusedElement.childNodes);
childNodes.forEach((node, index) => {
if (node.nodeType === Node.TEXT_NODE) {
textContent.push(node.textContent);
} else if (node.nodeName === 'BR') {
textContent.push('\n');
} else if (node.nodeName === 'P' || node.nodeName === 'DIV') {
if (index > -1) textContent.push('\n');
textContent.push(node.textContent);
}
});
text = textContent.join('');
}
if (text) {
navigator.clipboard.writeText(text).then(() => {
console.log("✅ 已复制输入框内容到剪贴板。");
showTemporaryFeedback(focusedElement, '复制成功');
}).catch(err => {
console.error("复制失败:", err);
alert("复制失败,请检查浏览器权限。");
});
}
// 确保输入框保持焦点
focusedElement.focus();
});
return button;
};

const createPasteButton = () => {
const button = document.createElement('button');
button.innerText = '🆅';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.title = '粘贴剪切板内容';
// 阻止mousedown默认行为以维持输入框焦点
button.addEventListener('mousedown', (e) => {
e.preventDefault();
});
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
try {
const clipboardText = await navigator.clipboard.readText();
// 使用现有的insertTextSmart函数插入文本
insertTextSmart(focusedElement, clipboardText);
// 添加视觉反馈
const originalText = button.innerText;
button.innerText = '✓';
button.style.backgroundColor = 'var(--success-color, #22c55e)';
button.style.color = 'white';
setTimeout(() => {
button.innerText = originalText;
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
}, 1000);
console.log("✅ 已粘贴剪贴板内容到输入框。");
} catch (err) {
console.error("访问剪切板失败:", err);
alert("粘贴失败,请检查浏览器权限。");
}
// 确保输入框保持焦点
focusedElement.focus();
});
return button;
};

const createClearButton = () => {
const button = document.createElement('button');
button.innerText = '✖️';
button.type = 'button';
button.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
button.style.color = 'var(--text-color, #333333)';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.marginLeft = '10px';
button.title = '清空输入框';
// 添加mousedown事件处理器来阻止焦点切换
button.addEventListener('mousedown', (e) => {
e.preventDefault();
});
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation(); // 阻止事件冒泡
const focusedElement = document.activeElement;
if (!focusedElement || !(focusedElement.tagName === 'TEXTAREA' || focusedElement.getAttribute('contenteditable') === 'true')) {
console.warn("当前未聚焦到有效的 textarea 或 contenteditable 元素。");
return;
}
// 使用现有的insertTextSmart函数清空内容
insertTextSmart(focusedElement, '', true);
// 确保立即重新聚焦
focusedElement.focus();
// 如果是textarea,还需要设置光标位置到开始处
if (focusedElement.tagName.toLowerCase() === 'textarea') {
focusedElement.selectionStart = focusedElement.selectionEnd = 0;
}
console.log("✅ 输入框内容已清空。");
showTemporaryFeedback(focusedElement, '清空成功');
});
return button;
};

// 新增的配置设置按钮和弹窗
const createConfigSettingsButton = () => {
const button = document.createElement('button');
button.innerText = '🛠️ 配置管理';
button.type = 'button';
button.style.backgroundColor = 'var(--info-color, #4F46E5)';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.padding = '5px 10px';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.addEventListener('click', showConfigSettingsDialog);
return button;
};

function exportConfig() {
const date = new Date();
const yyyy = date.getFullYear();
const mm = String(date.getMonth() + 1).padStart(2, '0');
const dd = String(date.getDate()).padStart(2, '0');
const fileName = `ChatGPT Template Text Folders [${yyyy}-${mm}-${dd}].json`;
const dataStr = JSON.stringify(buttonConfig, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
console.log("📤 配置已导出。");
}

function importConfig(rerenderFn) {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,application/json';
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (evt) => {
try {
const importedConfig = JSON.parse(evt.target.result);
if (importedConfig && typeof importedConfig === 'object') {
if (!importedConfig.folders || !importedConfig.folderOrder) {
alert('导入的配置文件无效!缺少必要字段。');
return;
}
// 替换现有配置
buttonConfig = importedConfig;
// 确保所有按钮都有'type'字段和'autoSubmit'字段
Object.entries(buttonConfig.folders).forEach(([folderName, folderConfig]) => {
Object.entries(folderConfig.buttons).forEach(([btnName, btnCfg]) => {
if (!btnCfg.type) {
if (folderName === "🖱️") {
btnCfg.type = "tool";
} else {
btnCfg.type = "template";
}
}
if (btnCfg.type === "template" && typeof btnCfg.autoSubmit !== 'boolean') {
btnCfg.autoSubmit = false;
}
});
});
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
if (rerenderFn) rerenderFn();
console.log("📥 配置已成功导入。");
// 更新按钮栏
updateButtonContainer();
} else {
alert('导入的配置文件内容无效!');
}
} catch (error) {
alert('导入的配置文件解析失败!请确认文件格式正确。');
}
};
reader.readAsText(file);
});
input.click();
}

// 新增的单独配置设置弹窗
const showConfigSettingsDialog = () => {
if (currentConfigOverlay) {
closeExistingOverlay(currentConfigOverlay);
}
const overlay = document.createElement('div');
overlay.classList.add('config-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 12000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('config-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 400px;
max-width: 90vw;
`;

dialog.innerHTML = `
<h3 style="margin:0 0 20px 0;font-size:18px;font-weight:600; color: var(--text-color, #333333);">🛠️ 配置管理</h3>
<div style="
display:flex;
flex-direction:column;
gap:20px;
margin-bottom:20px;
">
<!-- 重置按钮部分 -->
<div style="
display:flex;
flex-direction:row;
align-items:center;
padding-bottom:16px;
border-bottom:1px solid var(--border-color, #e5e7eb);
">
<span style="margin-right:12px;color: var(--text-color, #333333);">恢复默认设置:</span>
<button id="resetSettingsBtn" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius:4px;
">↩️ 重置</button>
</div>
<!-- 导入导出部分 -->
<div style="
display:flex;
flex-direction:row;
align-items:center;
">
<span style="margin-right:12px;color: var(--text-color, #333333);">配置导入导出:</span>
<div style="display:flex;gap:8px;">
<button id="importConfigBtn" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--add-color, #fd7e14);
color: white;
border-radius:4px;
">📥 导入</button>
<button id="exportConfigBtn" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--success-color, #22c55e);
color: white;
border-radius:4px;
">📤 导出</button>
</div>
</div>
</div>
<!-- 底部关闭按钮 -->
<div style="
display:flex;
justify-content:flex-end;
border-top:1px solid var(--border-color, #e5e7eb);
padding-top:16px;
">
<button id="closeConfigDialog" style="
${Object.entries(styles.button).map(([key, value]) => `${key}:${value}`).join(';')};
background-color: var(--cancel-color, #6B7280);
color: white;
border-radius:4px;
">关闭</button>
</div>
`;

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfigOverlay = overlay;

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

dialog.querySelector('#importConfigBtn').addEventListener('click', () => {
importConfig(() => {
// 重新渲染主设置面板
if (currentSettingsOverlay) {
selectedFolderName = buttonConfig.folderOrder[0] || null;
renderFolderList();
renderButtonList();
}
});
});

dialog.querySelector('#exportConfigBtn').addEventListener('click', exportConfig);

dialog.querySelector('#resetSettingsBtn').addEventListener('click', () => {
if (confirm('确认重置所有配置为默认设置吗?')) {
buttonConfig = JSON.parse(JSON.stringify(defaultConfig));
// 重置folderOrder
buttonConfig.folderOrder = Object.keys(buttonConfig.folders);
// 确保所有按钮都有'type'字段和'autoSubmit'字段
Object.entries(buttonConfig.folders).forEach(([folderName, folderConfig]) => {
Object.entries(folderConfig.buttons).forEach(([btnName, btnCfg]) => {
if (!btnCfg.type) {
if (folderName === "🖱️") {
btnCfg.type = "tool";
} else {
btnCfg.type = "template";
}
}
if (btnCfg.type === "template" && typeof btnCfg.autoSubmit !== 'boolean') {
btnCfg.autoSubmit = false;
}
});
});
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
if (currentSettingsOverlay) {
selectedFolderName = buttonConfig.folderOrder[0] || null;
renderFolderList();
renderButtonList();
}
alert('已重置为默认配置');
console.log("🔄 配置已重置为默认设置。");
// 更新按钮栏
updateButtonContainer();
}
});

dialog.querySelector('#closeConfigDialog').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfigOverlay = null;
});
};

let selectedFolderName = buttonConfig.folderOrder[0] || null; // 在设置面板中使用
let folderListContainer, buttonListContainer; // 在渲染函数中定义

const renderFolderList = () => {
if (!folderListContainer) return;
folderListContainer.innerHTML = '';
const foldersArray = buttonConfig.folderOrder.map(fname => [fname, buttonConfig.folders[fname]]).filter(([f,c])=>c);
foldersArray.forEach(([fname, fconfig]) => {
const folderItem = document.createElement('div');
folderItem.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-radius: 4px;
margin: 4px 0;
background-color: ${selectedFolderName === fname ? (isDarkMode() ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0,0,0,0.1)') : 'transparent'};
cursor: move;
`;
folderItem.classList.add('folder-item');
folderItem.setAttribute('draggable', 'true');
folderItem.setAttribute('data-folder', fname);

const {container: leftInfo, folderPreview} = (function createFolderPreview(fname, fconfig){
const container = document.createElement('div');
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.gap = '8px';
const folderPreview = document.createElement('button');
folderPreview.textContent = fname;
folderPreview.style.backgroundColor = fconfig.color;
folderPreview.style.color = fconfig.textColor || '#ffffff';
folderPreview.style.border = 'none';
folderPreview.style.borderRadius = '4px';
folderPreview.style.padding = '4px 8px';
folderPreview.style.fontSize = '14px';
folderPreview.style.cursor = 'grab';
container.appendChild(folderPreview);
return {container, folderPreview};
})(fname, fconfig);

const rightBtns = document.createElement('div');
rightBtns.style.display = 'flex';
rightBtns.style.gap = '20px';

// 创建编辑按钮
const editFolderBtn = document.createElement('button');
editFolderBtn.textContent = '✏️';
editFolderBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
font-size: 14px;
color: var(--primary-color, #3B82F6);
`;
editFolderBtn.addEventListener('click', (e) => {
e.stopPropagation();
showFolderEditDialog(fname, fconfig, (newFolderName) => {
selectedFolderName = newFolderName;
renderFolderList();
renderButtonList();
});
});

const deleteFolderBtn = document.createElement('button');
deleteFolderBtn.textContent = '🗑️';
deleteFolderBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
font-size: 14px;
color: var(--danger-color, #ef4444);
`;
deleteFolderBtn.addEventListener('click', (e) => {
e.stopPropagation();
showDeleteFolderConfirmDialog(fname, () => {
const allFolders = buttonConfig.folderOrder;
selectedFolderName = allFolders[0] || null;
renderFolderList();
renderButtonList();
});
});

rightBtns.appendChild(editFolderBtn);
rightBtns.appendChild(deleteFolderBtn);

folderItem.appendChild(leftInfo);
folderItem.appendChild(rightBtns);

folderItem.addEventListener('click', () => {
selectedFolderName = fname;
renderFolderList();
renderButtonList();
});

folderItem.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', fname);
folderItem.style.opacity = '0.5';
});

folderItem.addEventListener('dragover', (e) => {
e.preventDefault();
});

folderItem.addEventListener('dragenter', () => {
folderItem.style.border = `2px solid var(--primary-color, #3B82F6)`;
});

folderItem.addEventListener('dragleave', () => {
folderItem.style.border = 'none';
});

folderItem.addEventListener('drop', (e) => {
e.preventDefault();
const draggedFolder = e.dataTransfer.getData('text/plain');
if (draggedFolder && draggedFolder !== fname) {
const draggedIndex = buttonConfig.folderOrder.indexOf(draggedFolder);
const targetIndex = buttonConfig.folderOrder.indexOf(fname);
if (draggedIndex > -1 && targetIndex > -1) {
const [removed] = buttonConfig.folderOrder.splice(draggedIndex, 1);
buttonConfig.folderOrder.splice(targetIndex, 0, removed);
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
renderFolderList();
renderButtonList();
console.log(`🔄 文件夹顺序已更新:${draggedFolder} 移动到 ${fname} 前。`);
// 更新按钮栏
updateButtonContainer();
}
}
// Check if a button is being dropped onto this folder
const buttonData = e.dataTransfer.getData('application/json');
if (buttonData) {
try {
const { buttonName: draggedBtnName, sourceFolder } = JSON.parse(buttonData);
if (draggedBtnName && sourceFolder && sourceFolder !== fname) {
// Move the button from sourceFolder to fname
const button = buttonConfig.folders[sourceFolder].buttons[draggedBtnName];
if (button) {
// Remove from source
delete buttonConfig.folders[sourceFolder].buttons[draggedBtnName];
// Add to target
buttonConfig.folders[fname].buttons[draggedBtnName] = button;
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
renderFolderList();
renderButtonList();
console.log(`🔄 按钮 "${draggedBtnName}" 已从 "${sourceFolder}" 移动到 "${fname}"。`);
// Update button container
updateButtonContainer();
}
}
} catch (error) {
console.error("解析拖放数据失败:", error);
}
}
folderItem.style.border = 'none';
});

folderItem.addEventListener('dragend', () => {
folderItem.style.opacity = '1';
});

folderListContainer.appendChild(folderItem);
});
};

const renderButtonList = () => {
if (!buttonListContainer) return;
buttonListContainer.innerHTML = '';
if (!selectedFolderName) return;
const currentFolderConfig = buttonConfig.folders[selectedFolderName];
if (!currentFolderConfig) return;

const rightHeader = document.createElement('div');
rightHeader.style.display = 'flex';
rightHeader.style.justifyContent = 'space-between';
rightHeader.style.alignItems = 'center';
rightHeader.style.marginBottom = '8px';

const folderNameLabel = document.createElement('h3');
folderNameLabel.innerHTML = `➤ <strong>${selectedFolderName}</strong>`;
folderNameLabel.style.margin = '0';

const addNewButtonBtn = document.createElement('button');
Object.assign(addNewButtonBtn.style, styles.button, {
backgroundColor: 'var(--add-color, #fd7e14)',
color: 'white',
borderRadius: '4px'
});
addNewButtonBtn.textContent = '+ 新建按钮';
addNewButtonBtn.addEventListener('click', () => {
showButtonEditDialog(selectedFolderName, '', {}, () => {
renderButtonList();
});
});

rightHeader.appendChild(folderNameLabel);
rightHeader.appendChild(addNewButtonBtn);

buttonListContainer.appendChild(rightHeader);

const btnScrollArea = document.createElement('div');
btnScrollArea.style.flex = '1';
btnScrollArea.style.overflowY = 'auto';
btnScrollArea.style.border = `1px solid var(--border-color, #e5e7eb)`;
btnScrollArea.style.borderRadius = '4px';
btnScrollArea.style.padding = '8px';

const currentFolderButtons = Object.entries(currentFolderConfig.buttons);
const createButtonPreview = (btnName, btnCfg) => {
const btnEl = createCustomButtonElement(btnName, btnCfg);
btnEl.style.marginBottom = '0px';
btnEl.style.marginRight = '8px';
btnEl.style.cursor = 'grab';
return btnEl;
};

currentFolderButtons.forEach(([btnName, cfg]) => {
const btnItem = document.createElement('div');
btnItem.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding: 4px;
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 4px;
background-color: var(--button-bg, #f3f4f6);
cursor: move;
`;
btnItem.setAttribute('draggable', 'true');
btnItem.setAttribute('data-button-name', btnName);

const leftPart = document.createElement('div');
leftPart.style.display = 'flex';
leftPart.style.alignItems = 'center';
leftPart.style.gap = '8px';

const btnPreview = createButtonPreview(btnName, cfg);
leftPart.appendChild(btnPreview);

const opsDiv = document.createElement('div');
opsDiv.style.display = 'flex';
opsDiv.style.gap = '20px';

// 创建“自动提交”开关
const autoSubmitLabel = document.createElement('label');
autoSubmitLabel.style.display = 'flex';
autoSubmitLabel.style.alignItems = 'center';
autoSubmitLabel.style.gap = '6px';
autoSubmitLabel.style.cursor = 'pointer';
const autoSubmitCheckbox = document.createElement('input');
autoSubmitCheckbox.type = 'checkbox';
autoSubmitCheckbox.checked = cfg.autoSubmit || false;
autoSubmitCheckbox.style.cursor = 'pointer';
autoSubmitLabel.appendChild(autoSubmitCheckbox);
autoSubmitLabel.appendChild(document.createTextNode('自动提交'));
autoSubmitCheckbox.addEventListener('change', () => {
cfg.autoSubmit = autoSubmitCheckbox.checked;
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
console.log(`✅ 按钮 "${btnName}" 的自动提交已设置为 ${autoSubmitCheckbox.checked}`);
});

// 创建编辑按钮
const editBtn = document.createElement('button');
editBtn.textContent = '✏️';
editBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
color: var(--primary-color, #3B82F6);
font-size: 14px;
`;
editBtn.addEventListener('click', (e) => {
e.stopPropagation();
showButtonEditDialog(selectedFolderName, btnName, cfg, () => {
renderButtonList();
});
});

// 创建删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '🗑️';
deleteBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
color: var(--danger-color, #ef4444);
font-size: 14px;
`;
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
showDeleteButtonConfirmDialog(selectedFolderName, btnName, () => {
renderButtonList();
});
});

opsDiv.appendChild(autoSubmitLabel); // 添加“自动提交”开关
opsDiv.appendChild(editBtn);
opsDiv.appendChild(deleteBtn);

btnItem.appendChild(leftPart);
btnItem.appendChild(opsDiv);

btnItem.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('application/json', JSON.stringify({
buttonName: btnName,
sourceFolder: selectedFolderName
}));
btnItem.style.opacity = '0.5';
});

btnItem.addEventListener('dragover', (e) => {
e.preventDefault();
});

btnItem.addEventListener('dragenter', () => {
btnItem.style.border = `2px solid var(--primary-color, #3B82F6)`;
});

btnItem.addEventListener('dragleave', () => {
btnItem.style.border = `1px solid var(--border-color, #e5e7eb)`;
});

btnItem.addEventListener('drop', (e) => {
e.preventDefault();
const data = JSON.parse(e.dataTransfer.getData('application/json'));
const { buttonName: draggedBtnName } = data;
if (draggedBtnName && draggedBtnName !== btnName) {
const buttonsKeys = Object.keys(buttonConfig.folders[selectedFolderName].buttons);
const draggedIndex = buttonsKeys.indexOf(draggedBtnName);
const targetIndex = buttonsKeys.indexOf(btnName);
if (draggedIndex > -1 && targetIndex > -1) {
const reordered = [...buttonsKeys];
reordered.splice(draggedIndex, 1);
reordered.splice(targetIndex, 0, draggedBtnName);
const newOrderedMap = {};
reordered.forEach(k => {
newOrderedMap[k] = buttonConfig.folders[selectedFolderName].buttons[k];
});
buttonConfig.folders[selectedFolderName].buttons = newOrderedMap;
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
renderButtonList();
console.log(`🔄 按钮顺序已更新:${draggedBtnName} 移动到 ${btnName} 前。`);
// 更新按钮栏
updateButtonContainer();
}
}
btnItem.style.border = `1px solid var(--border-color, #e5e7eb)`;
});

btnItem.addEventListener('dragend', () => {
btnItem.style.opacity = '1';
});

btnScrollArea.appendChild(btnItem);
});

buttonListContainer.appendChild(btnScrollArea);
};

function updateButtonBarHeight(newHeight) {
const clamped = Math.min(150, Math.max(50, newHeight)); // 限制范围
buttonConfig.buttonBarHeight = clamped;
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));

// 更新容器高度
const container = document.querySelector('.folder-buttons-container');
if (container) {
container.style.height = clamped + 'px';
}
console.log("🔧 按钮栏高度已更新为", clamped, "px");
}

const showUnifiedSettingsDialog = () => {
if (currentSettingsOverlay) {
closeExistingOverlay(currentSettingsOverlay);
}
const overlay = document.createElement('div');
overlay.classList.add('settings-overlay');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 11000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog = document.createElement('div');
dialog.classList.add('settings-dialog');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 800px;
max-width: 95vw;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
`;

const header = document.createElement('div');
header.style.display = 'flex';
header.style.justifyContent = 'space-between';
header.style.alignItems = 'center';
header.style.marginBottom = '16px';

const title = document.createElement('h2');
title.textContent = "⚙️ 设置面板";
title.style.margin = '0';
title.style.fontSize = '20px';
title.style.fontWeight = '600';

const headerBtnsWrapper = document.createElement('div');
headerBtnsWrapper.style.display = 'flex';
headerBtnsWrapper.style.gap = '10px';

// 新建自动化按钮
const automationBtn = document.createElement('button');
automationBtn.innerText = '⚡ 自动化';
automationBtn.type = 'button';
automationBtn.style.backgroundColor = 'var(--info-color, #4F46E5)';
automationBtn.style.color = 'white';
automationBtn.style.border = 'none';
automationBtn.style.borderRadius = '4px';
automationBtn.style.padding = '5px 10px';
automationBtn.style.cursor = 'pointer';
automationBtn.style.fontSize = '14px';
automationBtn.addEventListener('click', () => {
showAutomationSettingsDialog();
});
headerBtnsWrapper.appendChild(automationBtn);

// 样式管理按钮
const styleMgmtBtn = document.createElement('button');
styleMgmtBtn.innerText = '🎨 样式管理';
styleMgmtBtn.type = 'button';
styleMgmtBtn.style.backgroundColor = 'var(--info-color, #4F46E5)';
styleMgmtBtn.style.color = 'white';
styleMgmtBtn.style.border = 'none';
styleMgmtBtn.style.borderRadius = '4px';
styleMgmtBtn.style.padding = '5px 10px';
styleMgmtBtn.style.cursor = 'pointer';
styleMgmtBtn.style.fontSize = '14px';
styleMgmtBtn.addEventListener('click', () => {
showStyleSettingsDialog();
});
headerBtnsWrapper.appendChild(styleMgmtBtn);

// 原有创建配置管理按钮
const openConfigBtn = createConfigSettingsButton();
headerBtnsWrapper.appendChild(openConfigBtn);

// 原有保存关闭按钮
const saveSettingsBtn = document.createElement('button');
Object.assign(saveSettingsBtn.style, styles.button, {
backgroundColor: 'var(--success-color, #22c55e)',
color: 'white',
borderRadius: '4px'
});
saveSettingsBtn.textContent = '💾 关闭并保存';
saveSettingsBtn.addEventListener('click', () => {
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentSettingsOverlay = null;
attachButtons();
console.log("✅ 设置已保存并关闭设置面板。");
updateButtonContainer();
});
headerBtnsWrapper.appendChild(saveSettingsBtn);

header.appendChild(title);
header.appendChild(headerBtnsWrapper);

const mainContainer = document.createElement('div');
mainContainer.style.display = 'flex';
mainContainer.style.flex = '1';
mainContainer.style.overflow = 'hidden';
mainContainer.style.borderTop = `1px solid var(--border-color, #e5e7eb)`;

const folderPanel = document.createElement('div');
folderPanel.style.display = 'flex';
folderPanel.style.flexDirection = 'column';
folderPanel.style.width = '220px';
folderPanel.style.borderRight = `1px solid var(--border-color, #e5e7eb)`;
folderPanel.style.marginRight = '12px';
folderPanel.style.overflowY = 'auto';
folderPanel.style.padding = '8px';

folderListContainer = document.createElement('div');
folderListContainer.style.flex = '1';
folderListContainer.style.overflowY = 'auto';
folderListContainer.style.padding = '8px';

const folderAddContainer = document.createElement('div');
folderAddContainer.style.padding = '8px';
folderAddContainer.style.borderTop = `1px solid var(--border-color, #e5e7eb)`;
folderAddContainer.style.display = 'flex';
folderAddContainer.style.justifyContent = 'center';

const addNewFolderBtn = document.createElement('button');
Object.assign(addNewFolderBtn.style, styles.button, {
backgroundColor: 'var(--add-color, #fd7e14)',
color: 'white',
borderRadius: '4px'
});
addNewFolderBtn.textContent = '+ 新建文件夹';
addNewFolderBtn.addEventListener('click', () => {
showFolderEditDialog('', {}, (newFolderName) => {
selectedFolderName = newFolderName;
renderFolderList();
renderButtonList();
console.log(`🆕 新建文件夹 "${newFolderName}" 已添加。`);
});
});
folderAddContainer.appendChild(addNewFolderBtn);

folderPanel.appendChild(folderListContainer);
folderPanel.appendChild(folderAddContainer);

buttonListContainer = document.createElement('div');
buttonListContainer.style.flex = '1';
buttonListContainer.style.overflowY = 'auto';
buttonListContainer.style.display = 'flex';
buttonListContainer.style.flexDirection = 'column';
buttonListContainer.style.padding = '8px';

renderFolderList();
renderButtonList();

mainContainer.appendChild(folderPanel);
mainContainer.appendChild(buttonListContainer);

const footer = document.createElement('div');
footer.style.display = 'none';
dialog.appendChild(header);
dialog.appendChild(mainContainer);
dialog.appendChild(footer);

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentSettingsOverlay = overlay;

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);
};

let currentAutomationOverlay = null;
/**
*
* 弹窗:自动化设置,显示所有 domainAutoSubmitSettings,并可删除、点击添加
*/
function showAutomationSettingsDialog() {
if (currentAutomationOverlay) {
closeExistingOverlay(currentAutomationOverlay);
}
const overlay = document.createElement('div');
overlay.classList.add('automation-overlay');
overlay.style.cssText = `
${Object.entries(styles.overlay).map(([k,v]) => `${k}:${v}`).join(';')};
z-index:12000;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
justify-content: center;
align-items: center;
opacity: 0;
`;

const dialog = document.createElement('div');
dialog.style.cssText = `
${Object.entries(styles.dialog).map(([k,v]) => `${k}:${v}`).join(';')};
width: 600px;
max-width: 90vw;
background-color: var(--dialog-bg, #ffffff);
`;
dialog.innerHTML = `
<h2 style="margin-top:0;">⚡ 自动化设置</h2>
<div style="text-align:right;margin-bottom:10px;">
<button id="closeAutomationBtn" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--success-color, #22c55e);
color: #fff;
">关闭并保存</button>
</div>
<div style="border:1px solid var(--border-color);padding:10px;border-radius:8px;max-height:300px;overflow:auto;">
<table style="width:100%;border-collapse:collapse;">
<thead>
<tr style="border-bottom:1px solid var(--border-color);">
<th style="text-align:left;padding:4px;">备注名称</th>
<th style="text-align:left;padding:4px;">网址</th>
<th style="text-align:left;padding:4px;">自动提交方式</th>
<th style="width:40px;"></th>
</tr>
</thead>
<tbody id="domainRuleTbody"></tbody>
</table>
</div>
<div style="margin-top:12px;text-align:right;">
<button id="addDomainRuleBtn" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--add-color, #fd7e14);
color: #fff;
">+ 添加</button>
</div>
`;

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentAutomationOverlay = overlay;

// 渲染列表
const domainRuleTbody = dialog.querySelector('#domainRuleTbody');
function renderDomainRules() {
domainRuleTbody.innerHTML = '';
buttonConfig.domainAutoSubmitSettings.forEach((rule, idx) => {
const tr = document.createElement('tr');
tr.style.borderBottom = '1px solid var(--border-color)';
tr.innerHTML = `
<td style="padding:4px;">${rule.name}</td>
<td style="padding:4px;">${rule.domain}</td>
<td style="padding:4px;">${rule.method}</td>
<td style="padding:4px;">
<button class="deleteRuleBtn" data-idx="${idx}" style="
background:none;border:none;cursor:pointer;color:var(--danger-color,#ef4444);
">🗑️</button>
</td>
`;
domainRuleTbody.appendChild(tr);
});
domainRuleTbody.querySelectorAll('.deleteRuleBtn').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
const delIdx = parseInt(btn.getAttribute('data-idx'), 10);
buttonConfig.domainAutoSubmitSettings.splice(delIdx, 1);
renderDomainRules();
});
});
}
renderDomainRules();

// 关闭并保存
dialog.querySelector('#closeAutomationBtn').addEventListener('click', () => {
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay);
currentAutomationOverlay = null;
});

// 添加新规则
dialog.querySelector('#addDomainRuleBtn').addEventListener('click', () => {
showAddDomainRuleDialog(() => {
renderDomainRules();
});
});

// 动画效果
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);
}

function showStyleSettingsDialog() {
// 若已存在同类弹窗则先关闭
if (currentConfirmOverlay) {
closeExistingOverlay(currentConfirmOverlay);
}

// 创建覆盖层
const overlay = document.createElement('div');
overlay.classList.add('style-overlay');
overlay.style.cssText = `
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 12000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

// 创建对话框
const dialog = document.createElement('div');
dialog.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 600px;
max-width: 95vw;
max-height: 80vh;
overflow-y: auto;
`;

// 标题
const title = document.createElement('h2');
title.textContent = '🎨 样式管理';
title.style.marginTop = '0';
dialog.appendChild(title);

// 说明文字
const desc = document.createElement('p');
desc.textContent = '您可根据不同网址,自定义按钮栏高度和注入CSS样式。';
dialog.appendChild(desc);

// 表格容器
const tableContainer = document.createElement('div');
tableContainer.style.cssText = `
border: 1px solid var(--border-color);
padding: 10px;
border-radius: 8px;
max-height: 300px;
overflow:auto;
margin-bottom: 12px;
`;

// 表格
const styleTable = document.createElement('table');
styleTable.style.width = '100%';
styleTable.style.borderCollapse = 'collapse';

// Thead
const thead = document.createElement('thead');
thead.innerHTML = `
<tr style="border-bottom:1px solid var(--border-color);">
<th style="text-align:left;padding:4px;">备注名称</th>
<th style="text-align:left;padding:4px;">网址</th>
<th style="text-align:left;padding:4px;">高度(px)</th>
<th style="text-align:left;padding:4px;">自定义CSS</th>
<th style="width:40px;"></th>
</tr>
`;
styleTable.appendChild(thead);

// Tbody
const tbody = document.createElement('tbody');
// 渲染 domainStyleSettings
function renderDomainStyles() {
tbody.innerHTML = '';
buttonConfig.domainStyleSettings.forEach((item, idx) => {
const tr = document.createElement('tr');
tr.style.borderBottom = '1px solid var(--border-color)';

tr.innerHTML = `
<td style="padding:4px;">${item.name || ''}</td>
<td style="padding:4px;">${item.domain || ''}</td>
<td style="padding:4px;">${item.height || ''}</td>
<td style="padding:4px;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"
title="${(item.cssCode || '').replace(/"/g, '&quot;')}">
${(item.cssCode || '').substring(0, 30).replace(/\n/g, ' ')}...
</td>
<td style="padding:4px;">
<button class="editStyleBtn" data-idx="${idx}" style="
background:none;border:none;cursor:pointer;color:var(--primary-color,#3B82F6);
">✏️</button>
<button class="deleteStyleBtn" data-idx="${idx}" style="
background:none;border:none;cursor:pointer;color:var(--danger-color,#ef4444);
">🗑️</button>
</td>
`;
tbody.appendChild(tr);
});

// 绑定删除和编辑事件
tbody.querySelectorAll('.deleteStyleBtn').forEach(btn => {
btn.addEventListener('click', (e) => {
const index = parseInt(btn.getAttribute('data-idx'), 10);
buttonConfig.domainStyleSettings.splice(index, 1);
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
renderDomainStyles(); // 重新渲染
});
});

tbody.querySelectorAll('.editStyleBtn').forEach(btn => {
btn.addEventListener('click', () => {
const index = parseInt(btn.getAttribute('data-idx'), 10);
showEditDomainStyleDialog(index);
});
});
}
renderDomainStyles();

styleTable.appendChild(tbody);
tableContainer.appendChild(styleTable);
dialog.appendChild(tableContainer);

// 新建按钮
const addStyleBtn = document.createElement('button');
addStyleBtn.textContent = '+ 新建';
addStyleBtn.style.cssText = `
background-color: var(--add-color, #fd7e14);
color: #fff;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
margin-bottom: 12px;
`;
addStyleBtn.addEventListener('click', () => {
showEditDomainStyleDialog(); // 无参表示新建
});
dialog.appendChild(addStyleBtn);

// 关闭按钮
const closeBtn = document.createElement('button');
closeBtn.textContent = '关闭';
closeBtn.style.cssText = `
background-color: var(--cancel-color, #6B7280);
color: white;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
float: right;
`;
closeBtn.addEventListener('click', () => {
closeExistingOverlay(overlay);
currentConfirmOverlay = null;
});
dialog.appendChild(closeBtn);

// 将对话框加到覆盖层
overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentConfirmOverlay = overlay;

// 动画进入
setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);

/**
* 新建/编辑域名样式对话框
* @param {number} index - 可选,若存在则为编辑,否则新建
*/
function showEditDomainStyleDialog(index) {
if (currentAddDomainOverlay) {
closeExistingOverlay(currentAddDomainOverlay);
}
const overlay2 = document.createElement('div');
overlay2.style.cssText = `
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(2px);
z-index: 13000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
`;

const dialog2 = document.createElement('div');
dialog2.style.cssText = `
background-color: var(--dialog-bg, #ffffff);
color: var(--text-color, #333333);
border-radius: 4px;
padding: 24px;
box-shadow: 0 8px 24px var(--shadow-color, rgba(0,0,0,0.1));
border: 1px solid var(--border-color, #e5e7eb);
transition: transform 0.3s ease, opacity 0.3s ease;
width: 400px;
max-width: 90vw;
`;

const isEdit = (typeof index === 'number');
const styleItem = isEdit
? { ...buttonConfig.domainStyleSettings[index] }
: {
domain: window.location.hostname, // 默认填充当前网址
name: document.title || '新样式', // 默认填充网页标题
height: 40,
cssCode: ''
};

const title2 = document.createElement('h3');
title2.textContent = isEdit ? '编辑自定义样式' : '新建自定义样式';
dialog2.appendChild(title2);

// 备注名称
const nameLabel = document.createElement('label');
nameLabel.textContent = '备注名称:';
const nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.value = styleItem.name;
nameInput.style.cssText = `
width:100%;margin-bottom:8px;padding:6px;border:1px solid var(--border-color);border-radius:4px;
`;
dialog2.appendChild(nameLabel);
dialog2.appendChild(nameInput);

// 网址
const domainLabel = document.createElement('label');
domainLabel.textContent = '网址:';
const domainInput = document.createElement('input');
domainInput.type = 'text';
domainInput.value = styleItem.domain;
domainInput.style.cssText = `
width:100%;margin-bottom:8px;padding:6px;border:1px solid var(--border-color);border-radius:4px;
`;
dialog2.appendChild(domainLabel);
dialog2.appendChild(domainInput);

// 高度
const heightLabel = document.createElement('label');
heightLabel.textContent = '按钮栏高度(px):';
const heightInput = document.createElement('input');
heightInput.type = 'number';
heightInput.min = '20';
heightInput.max = '200';
heightInput.value = styleItem.height;
heightInput.style.cssText = `
width:100%;margin-bottom:8px;padding:6px;border:1px solid var(--border-color);border-radius:4px;
`;
dialog2.appendChild(heightLabel);
dialog2.appendChild(heightInput);

// 自定义CSS
const cssLabel = document.createElement('label');
cssLabel.textContent = '自定义CSS:';
const cssTextarea = document.createElement('textarea');
cssTextarea.value = styleItem.cssCode;
cssTextarea.style.cssText = `
width:100%;height:80px;margin-bottom:8px;padding:6px;border:1px solid var(--border-color);
border-radius:4px;resize:vertical;
`;
dialog2.appendChild(cssLabel);
dialog2.appendChild(cssTextarea);

// 底部按钮
const footer2 = document.createElement('div');
footer2.style.textAlign = 'right';
footer2.style.display = 'flex';
footer2.style.justifyContent = 'flex-end';
footer2.style.gap = '12px';

const cancelBtn2 = document.createElement('button');
cancelBtn2.textContent = '取消';
cancelBtn2.style.cssText = `
background-color: var(--cancel-color, #6B7280);
color: #fff;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
`;
cancelBtn2.addEventListener('click', () => {
closeExistingOverlay(overlay2);
});

const saveBtn2 = document.createElement('button');
saveBtn2.textContent = isEdit ? '保存' : '创建';
saveBtn2.style.cssText = `
background-color: var(--success-color,#22c55e);
color: #fff;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
`;
saveBtn2.addEventListener('click', () => {
// 更新/创建
const updatedItem = {
domain: domainInput.value.trim(),
name: nameInput.value.trim() || '未命名样式',
height: parseInt(heightInput.value, 10) || 40,
cssCode: cssTextarea.value
};
// 校验省略,简单示例
if (isEdit) {
buttonConfig.domainStyleSettings[index] = updatedItem;
} else {
buttonConfig.domainStyleSettings.push(updatedItem);
}
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));
closeExistingOverlay(overlay2);
renderDomainStyles(); // 重新加载表格
});

footer2.appendChild(cancelBtn2);
footer2.appendChild(saveBtn2);
dialog2.appendChild(footer2);

overlay2.appendChild(dialog2);
document.body.appendChild(overlay2);
currentAddDomainOverlay = overlay2;

setTimeout(() => {
overlay2.style.opacity = '1';
dialog2.style.transform = 'scale(1)';
}, 10);
}
}

/** 子弹窗:添加新域名规则 */
let currentAddDomainOverlay = null;
function showAddDomainRuleDialog(onComplete) {
if (currentAddDomainOverlay) {
closeExistingOverlay(currentAddDomainOverlay);
}
const overlay = document.createElement('div');
overlay.classList.add('add-domain-overlay');
overlay.style.cssText = `
${Object.entries(styles.overlay).map(([k, v]) => `${k}:${v}`).join(';')};
z-index:13000;
background-color: var(--overlay-bg, rgba(0, 0, 0, 0.5));
justify-content: center;
align-items: center;
opacity: 0;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
${Object.entries(styles.dialog).map(([k, v]) => `${k}:${v}`).join(';')};
width: 400px;
max-width: 90vw;
background-color: var(--dialog-bg, #ffffff);
`;

dialog.innerHTML = `
<h3 style="margin-top:0;">添加新网址规则</h3>
<div style="display:flex;flex-direction:column;gap:8px; margin-bottom:8px;">
<label>网址:<input type="text" id="domainInput" style="
width:100%;padding:6px;border:1px solid var(--border-color);border-radius:4px;
" placeholder="例如:chatgpt.com" /></label>
<label>备注名称:<input type="text" id="domainNameInput" style="
width:100%;padding:6px;border:1px solid var(--border-color);border-radius:4px;
" placeholder="例如:ChatGPT" /></label>
<label>自动提交方式:
<select id="methodSelect" style="
width:100%;padding:6px;border:1px solid var(--border-color);border-radius:4px;
">
<option value="Enter">Enter</option>
<option value="Cmd+Enter">Cmd+Enter</option>
<option value="模拟点击提交按钮">模拟点击提交按钮</option>
</select>
</label>
</div>
<div style="text-align:right;">
<button id="cancelAddDomain" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--cancel-color,#6B7280);
color: #fff;
margin-right:8px;
">取消</button>
<button id="confirmAddDomain" style="
${Object.entries(styles.button).map(([k,v]) => `${k}:${v}`).join(';')};
background-color: var(--success-color,#22c55e);
color: #fff;
">确认</button>
</div>
`;

overlay.appendChild(dialog);
document.body.appendChild(overlay);
currentAddDomainOverlay = overlay;

dialog.querySelector('#cancelAddDomain').addEventListener('click', () => {
closeExistingOverlay(overlay);
currentAddDomainOverlay = null;
});

dialog.querySelector('#confirmAddDomain').addEventListener('click', () => {
const domainInput = dialog.querySelector('#domainInput').value.trim();
const domainName = dialog.querySelector('#domainNameInput').value.trim();
const method = dialog.querySelector('#methodSelect').value;

if (!domainInput || !domainName) {
alert("请输入网址和备注名称!");
return;
}
// 简易校验函数
if (!isValidDomainInput(domainInput)) {
alert("网址格式不正确,请仅输入域名或包含域名的链接,例如:chatgpt.com");
return;
}

buttonConfig.domainAutoSubmitSettings.push({
domain: domainInput,
name: domainName,
method: method
});
localStorage.setItem('chatGPTButtonFoldersConfig', JSON.stringify(buttonConfig));

closeExistingOverlay(overlay);
currentAddDomainOverlay = null;
if (onComplete) onComplete();
});

setTimeout(() => {
overlay.style.opacity = '1';
dialog.style.transform = 'scale(1)';
}, 10);
}

function isValidDomainInput(str) {
// 简易:包含'.' 不含空格 即视为有效
if (str.includes(' ')) return false;
if (!str.includes('.')) return false;
return true;
}

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
setCSSVariables(getCurrentTheme());
updateStylesOnThemeChange();
console.log("🌓 主题模式已切换,样式已更新。");
});

const createButtonContainer = () => {
let existingContainer = document.querySelector('.folder-buttons-container');
if (existingContainer) {
// 使用updateButtonContainer来处理已存在的容器
updateButtonContainer();
return existingContainer;
}
// 创建新容器
const buttonContainer = document.createElement('div');
buttonContainer.classList.add('folder-buttons-container');
buttonContainer.style.display = 'flex';
buttonContainer.style.flexWrap = 'nowrap'; // 改为不换行
buttonContainer.style.overflowX = 'auto'; // 横向滚动
buttonContainer.style.overflowY = 'hidden'; // 禁止纵向滚动
buttonContainer.style.gap = '10px';
buttonContainer.style.marginTop = '0px';
buttonContainer.style.height = buttonConfig.buttonBarHeight + 'px';
buttonContainer.style.scrollbarWidth = 'none'; // for Firefox
buttonContainer.style.msOverflowStyle = 'none'; // for IE/Edge
buttonContainer.classList.add('hide-scrollbar'); // 用于自定义::-webkit-scrollbar
buttonContainer.style.justifyContent = 'center';
buttonContainer.style.alignItems = 'center';
buttonContainer.style.padding = '6px 15px';
buttonContainer.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
buttonContainer.style.backgroundColor = 'var(--button-bg, #f3f4f6)';
buttonContainer.style.borderRadius = '4px';

// 添加所有文件夹按钮
buttonConfig.folderOrder.forEach((name) => {
const config = buttonConfig.folders[name];
if (config) {
const folderButton = createFolderButton(name, config);
buttonContainer.appendChild(folderButton);
}
});

// 按顺序添加功能按钮
// 现在所有工具按钮都在 '🖱️' 文件夹内,不再直接添加
// 仅添加设置和清空按钮
buttonContainer.appendChild(createSettingsButton());
buttonContainer.appendChild(createClearButton());

return buttonContainer;
};

const updateButtonContainer = () => {
let existingContainer = document.querySelector('.folder-buttons-container');
if (existingContainer) {
// 保存所有功能按钮的引用
const settingsButton = existingContainer.querySelector('button:nth-last-child(2)');
const clearButton = existingContainer.querySelector('button:last-child');

// 清空容器
existingContainer.innerHTML = '';

// 重新添加文件夹按钮
buttonConfig.folderOrder.forEach((name) => {
const config = buttonConfig.folders[name];
if (config) {
const folderButton = createFolderButton(name, config);
existingContainer.appendChild(folderButton);
}
});

// 按正确顺序重新添加功能按钮
if (settingsButton) existingContainer.appendChild(settingsButton);
if (clearButton) existingContainer.appendChild(clearButton);

console.log("✅ 按钮栏已更新。");
} else {
console.warn("⚠️ 未找到按钮容器,无法更新按钮栏。");
}
};

const attachButtonsToTextarea = (textarea) => {
// 仅附加一次按钮容器
let buttonContainer = document.querySelector('.folder-buttons-container');
if (!buttonContainer) {
buttonContainer = createButtonContainer();
// 插入按钮容器到 textarea 的父元素之后
// 根据ChatGPT的DOM结构,可能需要调整插入位置
textarea.parentElement.insertBefore(buttonContainer, textarea.nextSibling);
console.log("✅ 按钮容器已附加到 textarea 元素。");
} else {
console.log("ℹ️ 按钮容器已存在,跳过附加。");
}
textarea.addEventListener('contextmenu', (e) => {
e.preventDefault();
});
};

let attachTimeout;
const attachButtons = () => {
if (attachTimeout) clearTimeout(attachTimeout);
attachTimeout = setTimeout(() => {
const textareas = getAllTextareas();
console.log(`🔍 扫描到 ${textareas.length} 个 textarea 或 contenteditable 元素。`);
if (textareas.length === 0) {
console.warn("⚠️ 未找到任何 textarea 或 contenteditable 元素。");
return;
}
attachButtonsToTextarea(textareas[textareas.length - 1]);
console.log("✅ 按钮已附加到最新的 textarea 或 contenteditable 元素。");
}, 300);
};

const observeShadowRoots = (node) => {
if (node.shadowRoot) {
const shadowObserver = new MutationObserver(() => {
attachButtons();
});
shadowObserver.observe(node.shadowRoot, {
childList: true,
subtree: true,
});
node.shadowRoot.querySelectorAll('*').forEach(child => observeShadowRoots(child));
}
};

const initialize = () => {
attachButtons();
const observer = new MutationObserver((mutations) => {
let triggered = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
observeShadowRoots(node);
triggered = true;
}
});
}
});
if (triggered) {
attachButtons();
console.log("🔔 DOM 发生变化,尝试重新附加按钮。");
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
console.log("🔔 MutationObserver 已启动,监听 DOM 变化。");

(function applyDomainStyles() {
const currentHost = window.location.hostname;
// 找到第一个匹配的 domainStyleSettings (如需多个匹配,逻辑可自行修改)
const matchedStyle = buttonConfig.domainStyleSettings.find(s => currentHost.includes(s.domain));
if (matchedStyle) {
// 1) 更新按钮栏高度
const container = document.querySelector('.folder-buttons-container');
if (container) {
const clamped = Math.min(200, Math.max(20, matchedStyle.height || 40));
container.style.height = clamped + 'px';
console.log(`✅ 已根据 ${matchedStyle.name} 设置按钮栏高度:${clamped}px`);
}

// 2) 注入自定义CSS
if (matchedStyle.cssCode) {
const styleEl = document.createElement('style');
styleEl.setAttribute('data-domain-style', matchedStyle.domain);
styleEl.innerHTML = matchedStyle.cssCode;
document.head.appendChild(styleEl);
console.log(`✅ 已注入自定义CSS至 <head> 来自:${matchedStyle.name}`);
}
}
})();
};

window.addEventListener('load', () => {
console.log("⏳ 页面已完全加载,开始初始化脚本。");
initialize();
});

// 动态更新样式以适应主题变化
const updateStylesOnThemeChange = () => {
// Since we're using CSS variables, the styles are updated automatically
// Just update the button container to apply new styles
updateButtonContainer();
};

// Initial setting of CSS variables
setCSSVariables(getCurrentTheme());
})();

---分割线---

在修改油猴脚本后,
我在油猴脚本管理器中遇到了以下语法错误:
Syntax error?

请仔细分析代码,指出具体的语法错误位置,并提供修复建议。
同时,简要说明可能导致该错误的原因。

---分割线---

请按照以下规范,提供每一处代码修改:

1. 对每一处代码更改:
a. 使用 ```[编程语言] 格式的代码块
b. 在代码块前注明以下信息:
- 具体位置:[函数名] (行号范围)
- 修改类型:[新增/修改/删除]
- 更改说明:[简要说明更改的原因和目的]
c. 确保代码块包含足够的上下文,便于理解修改位置(至少包含修改前后的几行代码)
d. 代码块必须可直接复制使用,无需额外修改

2. 如有多处修改,请按照依赖关系排序,确保后续修改不会破坏前面的更改

3. 请包含必要的注释和文档更新建议,确保代码的可读性和可维护性

4. 示例格式:
位置:login_function (第24-30行)
修改类型:修改
更改说明:添加用户验证逻辑
```[语言]
// 包含足够上下文的代码块
```

Suy nghĩ

Investigating syntax errors

I’m digging into the syntax issue near line 90. The problem seems to be with the string 'var(--dialog-bg, #fff fff)', where 'fff' is split across lines, causing a syntax error.

Thought about script syntax issue,持续 5 秒

Trả lời

下面是对您贴出代码的快速检查结果,发现了确实存在一个明显的字符串拼接错误,导致语法解析失败。以下为问题原因分析和修复建议。


1. 导致语法错误的根本原因

styles.dialog 里,有一处 backgroundColor 错误地换行,形成了无效字符串:

backgroundColor: 'var(--dialog-bg, #fff fff)',

其中 #ffffff) 被不恰当地拆分成多行,破坏了 字符串的正确语法,导致 Syntax Error

修复前后的对比

  • 错误:

    backgroundColor: 'var(--dialog-bg, #fff fff)',

    其中 \n 造成了字符串分裂,油猴或浏览器在解析时就会抛出语法错误。

  • 正确:
    应该保持整段字符串在同一行,或至少拼接完整:

    backgroundColor: 'var(--dialog-bg, #ffffff)',

    或其它您期望的默认颜色写法。


2. 具体的修改示例(可直接复制粘贴)

根据您提供的代码,以下是最小化的改动定位和示例,行号可能需您根据实际脚本做微调(此处以大约第 93-97 行为例)。请务必在您实际的脚本中搜索相似上下文进行修改。

修改 1

位置styles 对象的 dialog 属性 (第90-100行左右)
修改类型修改
更改说明:修复 backgroundColor 的无效字符串,移除换行并改为正确的 #ffffff.

// ========== 修复前后的对比示例 ========== // 修复前:带有语法错误的换行 dialog: { position: 'relative', backgroundColor: 'var(--dialog-bg, #fff fff)', color: 'var(--text-color, #333333)', // ... }, // 修复后:合并为正确的一行 dialog: { position: 'relative', backgroundColor: 'var(--dialog-bg, #ffffff)', // <-- 修复 color: 'var(--text-color, #333333)', // ... },

原因:多行导致字符串意外中断,引起 SyntaxError。
建议:保证默认颜色 #ffffff 在同一行,并去掉多余换行。


3. 其他注意事项

  • 文档更新

    • 建议在此处添加注释标记,防止后续误操作再次拆分。
    • 也可统一使用 #fff#ffffff,并确保与脚本其他地方的默认值一致。
  • 语法严格模式

    • 油猴脚本默认启用“严格模式”,多余换行将使引擎报错。
    • 保证所有字符串在一行内或使用模板字符串也可,但必须注意反引号内的换行会被视为实际字符。

总结:只要您将该行 backgroundColor: 'var(--dialog-bg, #fff fff)' 修复为 backgroundColor: 'var(--dialog-bg, #ffffff)' (或其他正确用法) 即可解决语法错误。祝您使用愉快,若有任何疑问请再与我沟通。

Chia sẻ Q&A này