Greasy Fork is available in English.
全能整合版:在标题栏直观显示“存储模式”和“填充模式”。支持增量导入、分类折叠、Gemini 完美适配。新增面板折叠功能与UI自适应。
当前为
// ==UserScript==
// @name AI 提示词大师 Pro
// @namespace http://tampermonkey.net/
// @version 10.0.3
// @license MIT
// @description 全能整合版:在标题栏直观显示“存储模式”和“填充模式”。支持增量导入、分类折叠、Gemini 完美适配。新增面板折叠功能与UI自适应。
// @author WaterHuo
// @match *://gemini.google.com/*
// @match *://chatgpt.com/*
// @match *://claude.ai/*
// @match *://chat.deepseek.com/*
// @match *://www.doubao.com/*
// @match *://www.kimi.ai/*
// @match *://www.kimi.com/*
// @match *://kimi.moonshot.cn/*
// @match *://grok.com/*
// @match *://x.com/i/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect tfntmhg1.api.lncldglobal.com
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// ======= 0. 配置与工具函数 =======
const CONFIG = {
LC_ID: 'TFNtmHG1nSgEMcCEjz7HNGka-MdYXbMMI',
LC_KEY: '8J1H0edw3g8BPJaTLDgS4UDm',
API_URL: 'https://tfntmhg1.api.lncldglobal.com/1.1/classes/PromptData'
};
// 获取/生成用户唯一标识
const getUserId = () => {
let uid = GM_getValue('pm_uid');
if (!uid) {
uid = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
GM_setValue('pm_uid', uid);
}
return uid;
};
const USER_ID = getUserId();
// TrustedHTML 安全策略(适配Gemini)
let ttPolicy;
if (window.trustedTypes && window.trustedTypes.createPolicy) {
try {
const policyName = 'pm-policy-' + Math.random().toString(36).substring(7);
ttPolicy = window.trustedTypes.createPolicy(policyName, {
createHTML: (string) => string,
createScript: (string) => string
});
} catch (e) {
console.warn("PromptMaster: TrustedTypes policy already exists");
}
}
const setHTML = (el, html) => {
if (!el) return;
el.innerHTML = ttPolicy ? ttPolicy.createHTML(html) : html;
};
// ======= 1. 数据管理核心 =======
const STORAGE_KEY = 'pm_data_v16';
const MODE_KEY = 'pm_storage_mode';
const APPEND_KEY = 'pm_append_mode'; // 持久化追加模式
const CLOUD_OBJ_ID_KEY = 'pm_cloud_oid';
const FOLD_KEY = 'pm_folded_cats';
const MINIMIZED_KEY = 'pm_is_minimized';
let currentMode = GM_getValue(MODE_KEY, 'local'); // 默认本地存储
let promptData = {};
let foldedCats = GM_getValue(FOLD_KEY, []);
let isEditMode = false;
let appendMode = GM_getValue(APPEND_KEY, true); // 默认追加模式
let isMinimized = GM_getValue(MINIMIZED_KEY, false);
let isLoading = false;
// 默认初始数据
const defaultData = {
"写作类": [{ name: "📝 深度润色", content: "请优化以下文本,梳理逻辑脉络,提升语言表达的简洁性和流畅性,保留核心信息。" }],
"代码类": [{ name: "💻 逻辑审查", content: "请检查这段代码的语法错误、逻辑漏洞,给出优化建议和修复方案。" }]
};
// 云端API封装
const CloudAPI = {
headers: {
"X-LC-Id": CONFIG.LC_ID,
"X-LC-Key": CONFIG.LC_KEY,
"Content-Type": "application/json"
},
request(method, endpoint, data = null) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: method,
url: endpoint.startsWith('http') ? endpoint : CONFIG.API_URL + endpoint,
headers: this.headers,
data: data ? JSON.stringify(data) : null,
onload: (res) => {
if (res.status >= 200 && res.status < 300) {
resolve(JSON.parse(res.responseText));
} else {
reject(JSON.parse(res.responseText));
}
},
onerror: reject
});
});
},
// 获取云端数据
async fetchData() {
const res = await this.request('GET', `?where={"uid":"${USER_ID}"}`);
if (res.results && res.results.length > 0) {
GM_setValue(CLOUD_OBJ_ID_KEY, res.results[0].objectId);
return res.results[0].data;
}
return null;
},
// 保存数据到云端
async saveData(data) {
const oid = GM_getValue(CLOUD_OBJ_ID_KEY);
const payload = { uid: USER_ID, data: data };
if (oid) {
await this.request('PUT', `/${oid}`, payload);
} else {
const res = await this.request('POST', '', payload);
GM_setValue(CLOUD_OBJ_ID_KEY, res.objectId);
}
}
};
// 统一数据管理器(本地/云端切换)
const DataManager = {
async load() {
isLoading = true;
renderUI();
try {
if (currentMode === 'local') {
promptData = GM_getValue(STORAGE_KEY) || defaultData;
} else {
const cloudData = await CloudAPI.fetchData();
promptData = cloudData || defaultData;
}
} catch (e) {
toast(`加载失败: ${e.error || '网络错误'}`);
promptData = defaultData;
} finally {
isLoading = false;
renderUI();
}
},
async save() {
renderUI();
try {
if (currentMode === 'local') {
GM_setValue(STORAGE_KEY, promptData);
toast("✅ 本地已保存");
} else {
toast("☁️ 正在同步云端...", 5000);
await CloudAPI.saveData(promptData);
toast("✅ 云端同步完成");
}
} catch (e) {
toast("❌ 保存失败");
console.error(e);
}
}
};
// ======= 2. 导入导出工具 =======
const IOTools = {
// 导出JSON备份
exportJSON() {
const fileName = `prompt_master_${currentMode}_${new Date().toISOString().slice(0,10)}.json`;
const blob = new Blob([JSON.stringify(promptData, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
toast("✅ 已导出备份");
},
// 增量导入JSON
importJSON() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.style.display = 'none';
document.body.appendChild(input);
input.onchange = (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const newData = JSON.parse(event.target.result);
if (typeof newData === 'object' && newData !== null) {
let addedCount = 0;
Object.keys(newData).forEach(cat => {
if (!promptData[cat]) {
promptData[cat] = newData[cat];
addedCount += newData[cat].length;
} else {
const existingNames = new Set(promptData[cat].map(item => item.name));
newData[cat].forEach(newItem => {
if (!existingNames.has(newItem.name)) {
promptData[cat].push(newItem);
addedCount++;
}
});
}
});
if (addedCount > 0) {
DataManager.save();
alert(`✅ 增量导入成功!\n共新增 ${addedCount} 条模板。`);
} else {
alert("⚠️ 导入完成,没有新增内容。");
}
} else {
toast("❌ 导入格式错误");
}
} catch (err) {
toast("❌ 导入解析失败");
}
document.body.removeChild(input);
};
reader.readAsText(file);
};
input.click();
}
};
// ======= 3. 样式表 (状态栏增强 + 半透明预览 + 自适应布局) =======
GM_addStyle(`
#pm-root { font-family: -apple-system, system-ui, sans-serif; }
/* 主面板样式 - 改为自适应宽度 */
.pm-panel {
position: fixed;
top: 80px;
right: 20px;
width: auto; /* 允许宽度自动 */
min-width: 260px; /* 设置一个稍微宽一点的最小值 */
max-width: 350px; /* 防止过宽 */
background: #fff;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
z-index: 2147483647;
border: 1px solid #eee;
display: flex;
flex-direction: column;
transition: width 0.2s ease; /* 平滑过渡 */
}
/* 折叠悬浮球 */
.pm-float-ball {
position: fixed;
top: 80px;
right: 20px;
width: 40px;
height: 40px;
background: #1a73e8;
border-radius: 50%;
box-shadow: 0 4px 12px rgba(26,115,232,0.3);
z-index: 2147483647;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-weight: bold;
font-size: 14px;
border: 2px solid #fff;
transition: transform 0.2s, background 0.2s;
user-select: none;
}
.pm-float-ball:hover {
transform: scale(1.1);
background: #1557b0;
}
/* 标题栏布局 - 增加间距和防挤压 */
.pm-header {
padding: 10px 12px;
background: #fcfcfc;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 12px 12px 0 0;
flex-wrap: wrap; /* 允许必要时换行 */
gap: 8px; /* 元素间增加间距 */
}
.pm-title-area {
display: flex;
align-items: center;
gap: 6px; /* 增加标题和左侧徽章的间距 */
flex-wrap: nowrap;
flex: 1 1 auto;
}
.pm-title {
font-size: 14px; /* 稍微加大标题字号 */
font-weight: 700;
color: #1a73e8;
white-space: nowrap;
}
/* 状态徽章 */
.pm-badge {
font-size: 11px; /* 优化字体大小 */
padding: 3px 6px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
transition: 0.2s;
border: 1px solid transparent;
user-select: none;
white-space: nowrap; /* 防止徽章文字换行 */
line-height: 1.2;
}
.pm-badge:hover {
filter: brightness(0.95);
transform: translateY(-1px);
}
/* 存储模式配色 */
.mode-local { background: #e6f4ea; color: #137333; border-color: #ceead6; }
.mode-cloud { background: #e8f0fe; color: #1967d2; border-color: #d2e3fc; }
/* 填充模式配色 */
.fill-append { background: #f5f9ff; color: #406599; border-color: #e1eafc; }
.fill-replace { background: #f8f0f5; color: #8b5cf6; border-color: #f3e8ff; }
/* 标题栏右侧按钮区 */
.pm-header-right {
display: flex;
align-items: center;
gap: 4px; /* 按钮间距 */
flex: 0 0 auto; /* 保持不被压缩 */
}
.pm-icon-btn {
padding: 5px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
transition: 0.2s;
color: #5f6368;
}
.pm-icon-btn:hover {
background: #f1f3f4;
color: #1a73e8;
}
.pm-icon-active {
color: #1a73e8;
font-weight:bold;
background: #e8f0fe;
}
/* 内容区域 */
.pm-body {
padding: 8px;
max-height: 60vh;
overflow-y: auto;
scrollbar-width: thin;
position: relative;
min-height: 100px;
}
/* 底部工具栏 */
.pm-footer {
padding: 8px;
border-top: 1px solid #eee;
display: flex;
gap: 6px;
background: #fff;
border-radius: 0 0 12px 12px;
}
.pm-tool-btn {
flex: 1;
padding: 6px;
border: 1px solid #f1f3f4;
background: #f8f9fa;
border-radius: 6px;
font-size: 11px;
cursor: pointer;
color: #5f6368;
text-align: center;
transition:0.2s;
white-space: nowrap;
}
.pm-tool-btn:hover {
background: #e8f0fe;
color: #1967d2;
border-color: #d2e3fc;
}
/* 分类与模板列表 */
.pm-cat-wrap {
margin-bottom: 8px;
border-radius: 8px;
overflow: hidden;
}
.pm-cat-header {
display: flex;
align-items: center;
padding: 6px 4px;
background: #f8f9fa;
cursor: pointer;
position: relative;
}
.pm-cat-fold-icon {
font-size: 10px;
margin-right: 6px;
transition: transform 0.2s;
color: #70757a;
}
.pm-cat-name {
font-size: 12px; /* 字体微调 */
color: #5f6368;
font-weight: 700;
flex: 1;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.pm-cat-tools {
display: none;
gap: 6px;
margin-right: 4px;
}
.pm-cat-header:hover .pm-cat-tools {
display: flex;
}
.pm-tpl-list {
padding: 4px 0;
}
.pm-tpl-list.folded {
display: none;
}
.pm-item-wrap {
position: relative;
margin-bottom: 2px;
}
.pm-btn {
width: 100%;
border: none;
background: transparent;
padding: 8px 10px;
text-align: left;
font-size: 13px; /* 模板字体加大 */
border-radius: 6px;
cursor: pointer;
color: #3c4043;
transition: 0.2s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pm-btn:hover {
background: #f1f3f4;
color: #1a73e8;
}
/* 半透明预览浮窗 */
#pm-preview-float {
position: fixed;
display: none;
width: auto;
max-width: 280px;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(8px);
border: 1px solid rgba(0,0,0,0.08);
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
border-radius: 8px;
padding: 10px;
font-size: 12px;
line-height: 1.5;
color: #444;
z-index: 2147483647;
pointer-events: auto;
word-break: break-all;
transition: opacity 0.2s;
opacity: 0;
}
#pm-preview-float.show {
opacity: 1; /* 显示时不透明 */
}
/* 原地编辑器 */
.pm-inline-editor {
background: #fff;
border: 1px solid #1a73e8;
border-radius: 8px;
padding: 8px;
margin: 4px 0;
box-shadow: 0 4px 12px rgba(26,115,232,0.15);
}
.pm-inline-editor textarea {
width: 100%;
height: 100px;
border: 1px solid #dadce0;
border-radius: 4px;
padding: 6px;
font-size: 12px;
box-sizing: border-box;
resize: vertical;
margin: 5px 0;
}
.pm-inline-editor input, .pm-inline-editor select {
width: 100%;
border: 1px solid #dadce0;
border-radius: 4px;
padding: 4px 6px;
font-size: 12px;
box-sizing: border-box;
margin-bottom: 4px;
}
.pm-ed-btns {
display: flex;
justify-content: flex-end;
gap: 6px;
}
.pm-ebtn {
padding: 3px 8px;
font-size: 11px;
border-radius: 4px;
cursor: pointer;
border: none;
}
.pm-save {
background: #1a73e8;
color: #fff;
}
.pm-cancel {
background: #f1f3f4;
color: #5f6368;
}
/* 加载中遮罩 */
.pm-loading {
position: absolute;
top:0;
left:0;
width:100%;
height:100%;
background:rgba(255,255,255,0.8);
display:flex;
justify-content:center;
align-items:center;
font-size:12px;
color:#666;
z-index:10;
}
/* 提示弹窗 */
.pm-toast {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: #323232;
color: #fff;
padding: 6px 16px;
border-radius: 16px;
font-size: 12px;
z-index: 2147483647;
display: none;
}
`);
// ======= 4. 稳定填充核心 =======
async function stableInject(text) {
// 兼容多平台输入框选择器
const inputField = document.querySelector(
'div[role="textbox"], #prompt-textarea, textarea[placeholder*="输入"], #chat-input, [contenteditable="true"], textarea'
);
if (!inputField) return toast("❌ 未找到输入框");
inputField.focus();
const isRich = inputField.isContentEditable;
const oldVal = isRich ? inputField.innerText : inputField.value;
const newVal = (appendMode && oldVal.trim()) ? (oldVal + "\n" + text) : text;
try {
if (isRich) {
// 富文本输入框处理
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(inputField);
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('delete', false);
document.execCommand('insertText', false, newVal);
} else {
// 普通文本框处理(绕过输入限制)
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
setter.call(inputField, newVal);
inputField.dispatchEvent(new Event('input', { bubbles: true }));
}
toast("✅ 填充成功");
inputField.focus();
// 滚动到输入框底部
setTimeout(() => { inputField.scrollTop = inputField.scrollHeight; }, 10);
} catch (e) {
toast("❌ 尝试填充失败");
}
}
// ======= 5. UI 渲染引擎 =======
function toast(msg, time=2000) {
const t = document.getElementById('pm-toast') || (() => {
const d = document.createElement('div');
d.id = 'pm-toast';
d.className = 'pm-toast';
document.body.appendChild(d);
return d;
})();
setHTML(t, msg);
t.style.display = 'block';
setTimeout(() => t.style.display = 'none', time);
}
function renderUI() {
if (!document.body) return;
// 创建根容器和预览浮窗
let root = document.getElementById('pm-root');
if (!root) {
root = document.createElement('div');
root.id = 'pm-root';
document.body.appendChild(root);
const pf = document.createElement('div');
pf.id = 'pm-preview-float';
document.body.appendChild(pf);
}
const previewFloat = document.getElementById('pm-preview-float');
// 如果处于折叠状态,只渲染悬浮球
if (isMinimized) {
setHTML(root, `<div class="pm-float-ball" id="pm-restore-btn" title="点击展开">AI</div>`);
document.getElementById('pm-restore-btn').onclick = () => {
isMinimized = false;
GM_setValue(MINIMIZED_KEY, false);
renderUI();
};
return;
}
// 样式逻辑(动态生成状态徽章类名/文本)
const modeClass = currentMode === 'local' ? 'mode-local' : 'mode-cloud';
const modeText = currentMode === 'local' ? '🏠 本地' : '☁️ 云端';
const fillClass = appendMode ? 'fill-append' : 'fill-replace';
const fillText = appendMode ? '➕ 追加' : '🔄 替换';
const configIcon = isEditMode ? '✅' : '⚙️';
// 渲染主面板
setHTML(root, `
<div class="pm-panel">
<div class="pm-header">
<div class="pm-title-area">
<span class="pm-title">提示词大师</span>
<span id="pm-switch-storage" class="pm-badge ${modeClass}" title="切换存储模式">${modeText}</span>
</div>
<div class="pm-header-right">
<span id="pm-switch-fill" class="pm-badge ${fillClass}" title="切换填充模式">${fillText}</span>
<span id="pm-config-btn" class="pm-icon-btn ${isEditMode?'pm-icon-active':''}" title="编辑管理">${configIcon}</span>
<span id="pm-fold-btn" class="pm-icon-btn" title="折叠面板" style="font-weight:bold; margin-left:2px;">−</span>
</div>
</div>
<div class="pm-body" id="pm-list-container">
${isLoading ? '<div class="pm-loading">同步中...</div>' : ''}
</div>
<div class="pm-footer">
<button class="pm-tool-btn" id="pm-export-btn">📤 导出</button>
<button class="pm-tool-btn" id="pm-import-btn">📥 导入</button>
${isEditMode ? `<button class="pm-tool-btn" id="pm-new-cat" style="background:#e8f0fe; color:#1a73e8;">+ 分类</button>` : ''}
</div>
</div>
`);
// 绑定折叠按钮事件
document.getElementById('pm-fold-btn').onclick = () => {
isMinimized = true;
GM_setValue(MINIMIZED_KEY, true);
renderUI();
};
if (isLoading) return;
// 渲染分类和模板列表
const container = document.getElementById('pm-list-container');
Object.keys(promptData).forEach(cat => {
const isFolded = foldedCats.includes(cat);
const catWrap = document.createElement('div');
catWrap.className = 'pm-cat-wrap';
// 分类头部(折叠/编辑)
const header = document.createElement('div');
header.className = 'pm-cat-header';
setHTML(header, `
<span class="pm-cat-fold-icon" style="transform: ${isFolded ? 'rotate(-90deg)' : 'rotate(0deg)'}">▼</span>
<span class="pm-cat-name">${cat}</span>
${isEditMode ? `<div class="pm-cat-tools">
<span class="pm-ed-cat" data-cat="${cat}">✏️</span>
<span class="pm-del-cat" data-cat="${cat}">×</span>
</div>` : ''}
`);
// 分类折叠/展开逻辑
header.onclick = (e) => {
if (e.target.closest('.pm-cat-tools')) return;
if (isFolded) {
foldedCats = foldedCats.filter(c => c !== cat);
} else {
foldedCats.push(cat);
}
GM_setValue(FOLD_KEY, foldedCats);
renderUI();
};
// 分类编辑/删除事件
if (isEditMode) {
header.querySelector('.pm-ed-cat').onclick = () => editCatName(catWrap, cat);
header.querySelector('.pm-del-cat').onclick = () => deleteCat(cat);
}
catWrap.appendChild(header);
// 模板列表
const tplList = document.createElement('div');
tplList.className = `pm-tpl-list ${isFolded ? 'folded' : ''}`;
// 渲染单个模板
promptData[cat].forEach((item, idx) => {
const itemWrap = document.createElement('div');
itemWrap.className = 'pm-item-wrap';
const btn = document.createElement('button');
btn.className = 'pm-btn';
btn.innerText = item.name;
// 预览浮窗逻辑
btn.onmouseenter = (e) => {
if (isEditMode) return;
const rect = btn.getBoundingClientRect();
// 显示模板名称 + 150字预览
const nameText = `<div style="font-weight: 600; color: #1a73e8; margin-bottom: 6px; border-bottom: 1px solid rgba(0,0,0,0.05); padding-bottom: 4px;">${item.name}</div>`;
const coreText = item.content.length > 150 ? item.content.substring(0, 150) + "..." : item.content;
setHTML(previewFloat, `${nameText}${coreText.replace(/\n/g, '<br>')}`);
// 浮窗显示+动画
previewFloat.style.display = 'block';
previewFloat.classList.add('show');
// 优化浮窗位置(避免出界)
let topPos = rect.top + window.scrollY;
let leftPos = rect.right + 15;
// 右侧出界则显示在左侧
if (leftPos + previewFloat.offsetWidth > window.innerWidth) {
leftPos = rect.left - previewFloat.offsetWidth - 15;
}
// 底部出界则向上调整
if (topPos + previewFloat.offsetHeight > window.innerHeight + window.scrollY) {
topPos = rect.bottom - previewFloat.offsetHeight + window.scrollY;
}
previewFloat.style.top = `${topPos}px`;
previewFloat.style.left = `${leftPos}px`;
previewFloat.style.right = 'auto';
};
// 鼠标离开隐藏浮窗(带过渡动画)
btn.onmouseleave = () => {
previewFloat.classList.remove('show');
setTimeout(() => {
previewFloat.style.display = 'none';
}, 150);
};
// 模板点击事件(编辑/填充)
btn.onclick = () => {
if (isEditMode) {
editTpl(itemWrap, cat, idx);
} else {
stableInject(item.content);
previewFloat.style.display = 'none';
}
};
itemWrap.appendChild(btn);
tplList.appendChild(itemWrap);
});
// 编辑模式下添加新模板按钮
if (isEditMode) {
const addTplBtn = document.createElement('button');
addTplBtn.className = 'pm-btn';
addTplBtn.style.border = "1px dashed #ccc";
addTplBtn.innerText = "+ 新模板";
addTplBtn.onclick = () => editTpl(tplList, cat, -1, addTplBtn);
tplList.appendChild(addTplBtn);
}
catWrap.appendChild(tplList);
container.appendChild(catWrap);
});
// 事件绑定
// 编辑模式切换
document.getElementById('pm-config-btn').onclick = () => {
isEditMode = !isEditMode;
renderUI();
};
// 填充模式切换(持久化)
document.getElementById('pm-switch-fill').onclick = () => {
appendMode = !appendMode;
GM_setValue('pm_append_mode', appendMode);
renderUI();
};
// 存储模式切换
document.getElementById('pm-switch-storage').onclick = async () => {
if (isLoading) return;
const targetMode = currentMode === 'local' ? '☁️ 云端' : '🏠 本地';
if (confirm(`确认切换至 [${targetMode}] 模式?`)) {
currentMode = currentMode === 'local' ? 'cloud' : 'local';
GM_setValue(MODE_KEY, currentMode);
await DataManager.load();
}
};
// 导入/导出事件
document.getElementById('pm-export-btn').onclick = IOTools.exportJSON;
document.getElementById('pm-import-btn').onclick = IOTools.importJSON;
// 新建分类事件
if (isEditMode) {
document.getElementById('pm-new-cat').onclick = () => {
const n = prompt("请输入新分类名称:");
if (n) {
promptData[n] = [];
DataManager.save();
}
};
}
}
// ======= 6. 辅助编辑逻辑 =======
// 编辑分类名称
function editCatName(wrap, oldName) {
const header = wrap.querySelector('.pm-cat-header');
header.style.display = 'none';
const editor = document.createElement('div');
editor.className = 'pm-inline-editor';
setHTML(editor, `
<input type="text" id="new-cat-inp" value="${oldName}">
<div class="pm-ed-btns">
<button class="pm-ebtn pm-cancel">取消</button>
<button class="pm-ebtn pm-save">保存</button>
</div>
`);
wrap.prepend(editor);
editor.querySelector('.pm-cancel').onclick = () => renderUI();
editor.querySelector('.pm-save').onclick = () => {
const n = editor.querySelector('#new-cat-inp').value.trim();
if (n && n !== oldName) {
promptData[n] = promptData[oldName];
delete promptData[oldName];
DataManager.save();
} else {
renderUI();
}
};
}
// 删除分类
function deleteCat(catName) {
if (!confirm(`确定删除分类 [${catName}] 吗?删除后该分类下的所有模板也会被移除。`)) return;
delete promptData[catName];
DataManager.save();
}
// 编辑/新增模板
function editTpl(container, cat, idx, addBtn = null) {
const isNew = idx === -1;
const item = isNew ? { name: "", content: "" } : promptData[cat][idx];
const editor = document.createElement('div');
editor.className = 'pm-inline-editor';
// 生成分类下拉选项
let cats = Object.keys(promptData).map(c =>
`<option value="${c}" ${c === cat ? 'selected' : ''}>${c}</option>`
).join('');
setHTML(editor, `
<input type="text" id="ed-name" placeholder="模板名称" value="${item.name}">
<select id="ed-cat">${cats}</select>
<div style="position:relative">
<textarea id="ed-cont" placeholder="提示词内容...">${item.content}</textarea>
</div>
<div class="pm-ed-btns">
<button class="pm-ebtn pm-cancel">取消</button>
<button class="pm-ebtn pm-save">保存</button>
</div>
`);
// 隐藏原模板按钮/新增按钮
if (!isNew) container.querySelector('.pm-btn').style.display = 'none';
if (addBtn) addBtn.style.display = 'none';
container.appendChild(editor);
// 取消编辑
editor.querySelector('.pm-cancel').onclick = () => renderUI();
// 保存模板
editor.querySelector('.pm-save').onclick = () => {
const n = editor.querySelector('#ed-name').value.trim();
const c = editor.querySelector('#ed-cont').value;
const tCat = editor.querySelector('#ed-cat').value;
if (!n || !c) {
toast("❌ 名称和内容不能为空");
return;
}
// 编辑模式:删除原模板
if (!isNew) promptData[cat].splice(idx, 1);
// 添加新模板
promptData[tCat].push({ name: n, content: c });
DataManager.save();
};
}
// ======= 7. 启动初始化 =======
const init = async () => {
if (!document.getElementById('pm-root')) {
renderUI();
await DataManager.load();
}
};
// 监听页面加载,确保UI正确渲染
const observer = new MutationObserver(() => {
if (!document.getElementById('pm-root')) renderUI();
});
const startObserver = () => {
if (document.body) {
observer.observe(document.body, { childList: true, subtree: false });
init();
} else {
setTimeout(startObserver, 100);
}
};
startObserver();
// 兜底检查(防止Observer失效)
setInterval(() => {
if (document.body && !document.getElementById('pm-root')) renderUI();
}, 2000);
})();