- // ==UserScript==
- // @name Prompt Manager
- // @namespace http://tampermonkey.net/
- // @version 1.0
- // @description Fixed focus issue with search, import, and export (search below list)
- // @author yowaimono
- // @match https://grok.com/chat/*
- // @match https://askmanyai.cn/chat/*
- // @match https://chat.deepseek.com/a/chat/*
- // @match https://yuanbao.tencent.com/*
- // @match https://kimi.moonshot.cn/chat/*
- // @match https://www.wenxiaobai.com/chat/*
- // @match https://chatgpt.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
- // @grant none
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // 配置数据
- const STORAGE_KEY = 'promptManagerData';
- let prompts = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
- let filteredPrompts = [...prompts]; // 用于存储搜索结果
- let currentEditIndex = null;
- let isMinimized = false;
- let lastFocusedElement = null; // 新增:记录最后聚焦的元素
-
- // 创建主容器
- const container = document.createElement('div');
- container.id = 'prompt-manager';
- container.innerHTML = `
- <div class="pm-header">
- <h3>提示词管理</h3>
- <div class="pm-header-buttons">
- <button class="pm-icon-btn" id="pm-export">
- <span class="pm-icon">⬇</span>
- </button>
- <button class="pm-icon-btn" id="pm-import">
- <span class="pm-icon">⬆</span>
- </button>
- <button class="pm-icon-btn" id="pm-minimize">
- <span class="pm-icon">-</span>
- </button>
- <button class="pm-icon-btn" id="pm-add">
- <span class="pm-icon">+</span>
- </button>
- </div>
- </div>
- <div class="pm-list" id="pm-list"></div>
- <div class="pm-search-container">
- <input type="text" id="pm-search" placeholder="搜索提示词" class="pm-input">
- </div>
-
- <div class="pm-modal" id="pm-modal">
- <div class="pm-modal-content">
- <div class="pm-modal-header">
- <h4>${currentEditIndex !== null ? '编辑提示词' : '新建提示词'}</h4>
- <button class="pm-icon-btn" id="pm-close">
- <span class="pm-icon">×</span>
- </button>
- </div>
- <div class="pm-modal-body">
- <input type="text" id="pm-title" placeholder="请输入标题" class="pm-input">
- <textarea id="pm-content" placeholder="请输入内容" class="pm-textarea"></textarea>
- </div>
- <div class="pm-modal-footer">
- <button class="pm-btn pm-primary" id="pm-save">保存</button>
- <button class="pm-btn" id="pm-cancel">取消</button>
- </div>
- </div>
- </div>
- <input type="file" id="pm-import-file" style="display: none;" accept=".json">
- `;
- document.body.appendChild(container);
-
- // 主样式(完全保持原样)
- const style = document.createElement('style');
- style.textContent = `
- #prompt-manager {
- position: fixed;
- top: 20px;
- right: 20px;
- width: 320px;
- background: #fff;
- border-radius: 12px;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
- z-index: 9999;
- font-family: 'Helvetica Neue', Arial, sans-serif;
- transition: all 0.3s ease;
- }
-
- #prompt-manager.minimized {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- overflow: hidden;
- cursor: pointer;
- }
-
- #prompt-manager.minimized .pm-header {
- border-bottom: none;
- padding: 0;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- #prompt-manager.minimized .pm-header h3 {
- display: none;
- }
-
- #prompt-manager.minimized .pm-header-buttons {
- display: none;
- }
-
- #prompt-manager.minimized .pm-list,
- #prompt-manager.minimized .pm-modal,
- #prompt-manager.minimized .pm-search-container {
- display: none !important;
- }
-
- #prompt-manager.minimized .pm-header::before {
- content: 'AI';
- color: #1890ff;
- font-size: 16px;
- font-weight: bold;
- }
-
- .pm-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px;
- border-bottom: 1px solid #f0f0f0;
- }
-
- .pm-header h3 {
- margin: 0;
- font-size: 16px;
- color: #1f1f1f;
- }
-
- .pm-header-buttons {
- display: flex;
- gap: 8px;
- align-items: center; /* 垂直居中 */
- }
-
- .pm-icon-btn {
- width: 32px;
- height: 32px;
- border: none;
- background: #1890ff;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: all 0.2s;
- color: #fff;
- }
-
- .pm-icon-btn:hover {
- background: #40a9ff;
- }
-
- .pm-icon-btn#pm-minimize {
- background: #blue;
- color: blue;
- border: 1px solid #1890ff;
- font-size: 14px;
- font-weight: bold;
- }
-
- .pm-icon {
- color: #fff;
- font-size: 20px;
- line-height: 1;
- }
-
- .pm-list {
- max-height: 150px; /* 固定高度为200px */
- overflow-y: auto;
- padding: 8px;
- }
-
- .pm-item {
- display: flex;
- align-items: center;
- padding: 12px;
- margin: 4px;
- background: #f8fafb;
- border-radius: 8px;
- transition: background 0.2s;
- cursor: pointer;
- }
-
- .pm-item:hover {
- background: #e6f4ff;
- }
-
- .pm-item-title {
- flex: 1;
- font-size: 14px;
- color: #434343;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .pm-item-actions {
- display: flex;
- gap: 8px;
- opacity: 0;
- transition: opacity 0.2s;
- }
-
- .pm-item:hover .pm-item-actions {
- opacity: 1;
- }
-
- .pm-modal {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.4);
- justify-content: center;
- align-items: center;
- overflow: auto;
- }
-
- .pm-modal-content {
- background: #fff;
- width: 440px;
- border-radius: 12px;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
- max-height: 90vh;
- overflow: auto;
- }
-
- .pm-modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px;
- border-bottom: 1px solid #f0f0f0;
- }
-
- .pm-modal-header h4 {
- margin: 0;
- font-size: 16px;
- color: #1d1d1d;
- }
-
- .pm-modal-body {
- padding: 16px;
- }
-
- .pm-input, .pm-textarea {
- width: 90%;
- padding: 8px 12px;
- border: 1px solid #e0e0e0;
- border-radius: 6px;
- margin: 8px 0;
- font-size: 14px;
- transition: border-color 0.2s;
- }
-
- .pm-input:focus, .pm-textarea:focus {
- border-color: #1890ff;
- outline: none;
- }
-
- .pm-textarea {
- height: 100px;
- resize: vertical;
- }
-
- .pm-modal-footer {
- padding: 16px;
- text-align: right;
- border-top: 1px solid #f0f0f0;
- }
-
- .pm-btn {
- padding: 8px 16px;
- border: none;
- border-radius: 6px;
- cursor: pointer;
- font-size: 14px;
- transition: all 0.2s;
- }
-
- .pm-primary {
- background: #1890ff;
- color: #fff;
- }
-
- .pm-primary:hover {
- background: #40a9ff;
- }
-
- .pm-btn:not(.pm-primary) {
- background: #f5f5f5;
- color: #666;
- margin-left: 8px;
- }
-
- .pm-btn:not(.pm-primary):hover {
- background: #e0e0e0;
- }
-
- .pm-search-container {
- padding: 8px;
- border-top: 1px solid #f0f0f0;
- }
- `;
- document.head.appendChild(style);
-
- // 新增:焦点追踪逻辑
- document.addEventListener('focusin', (e) => {
- if (!container.contains(e.target)) {
- const target = e.target;
- if (target.matches('input, textarea, [contenteditable="true"]')) {
- lastFocusedElement = target;
- }
- }
- });
-
- // 渲染列表
- function renderList() {
- const list = document.getElementById('pm-list');
- list.innerHTML = ''; // 清空列表
-
- filteredPrompts.forEach((item, index) => {
- const listItem = document.createElement('div');
- listItem.classList.add('pm-item');
- listItem.innerHTML = `
- <span class="pm-item-title">${item.title}</span>
- <div class="pm-item-actions">
- <button class="pm-icon-btn" style="background:#52c41a">
- <span class="pm-icon">✎</span>
- </button>
- <button class="pm-icon-btn" style="background:#ff4d4f">
- <span class="pm-icon">✕</span>
- </button>
- </div>
- `;
-
- // 修改事件处理
- const [editBtn, deleteBtn] = listItem.querySelectorAll('.pm-icon-btn');
-
- // 阻止按钮获取焦点
- editBtn.addEventListener('mousedown', e => e.preventDefault());
- deleteBtn.addEventListener('mousedown', e => e.preventDefault());
-
- editBtn.addEventListener('click', (e) => {
- e.stopPropagation();
- editPrompt(prompts.indexOf(item)); // 传递原始索引
- });
-
- deleteBtn.addEventListener('click', (e) => {
- e.stopPropagation();
- deletePrompt(prompts.indexOf(item)); // 传递原始索引
- });
-
- // 阻止列表项获取焦点
- listItem.addEventListener('mousedown', e => e.preventDefault());
-
- listItem.addEventListener('click', () => {
- if (lastFocusedElement) {
- pasteToFocusedElement(item.content);
- } else {
- alert('请先点击需要输入的位置');
- }
- });
-
- list.appendChild(listItem);
- });
- }
-
- // 修改粘贴函数
- function pasteToFocusedElement(text) {
- if (!lastFocusedElement) return;
-
- try {
- // 强制恢复焦点
- lastFocusedElement.focus();
-
- if (lastFocusedElement.isContentEditable) {
- const selection = window.getSelection();
- const range = selection.getRangeAt(0);
- range.deleteContents();
- const textNode = document.createTextNode(text);
- range.insertNode(textNode);
- range.setStartAfter(textNode);
- selection.collapseToEnd();
- } else {
- const elem = lastFocusedElement;
- const start = elem.selectionStart;
- elem.setRangeText(text, start, start, 'end');
- elem.selectionStart = elem.selectionEnd = start + text.length;
- }
-
- // 触发输入事件
- const event = new Event('input', { bubbles: true });
- lastFocusedElement.dispatchEvent(event);
- } catch (error) {
- console.error('粘贴失败,使用剪贴板回退');
- navigator.clipboard.writeText(text);
- alert('已复制到剪贴板,请手动粘贴');
- }
- }
-
- // 编辑提示词
- window.editPrompt = function(index) {
- currentEditIndex = index;
- document.getElementById('pm-title').value = prompts[index].title;
- document.getElementById('pm-content').value = prompts[index].content;
- document.getElementById('pm-modal').style.display = 'flex';
- };
-
- // 删除提示词
- window.deletePrompt = function(index) {
- prompts.splice(index, 1);
- localStorage.setItem(STORAGE_KEY, JSON.stringify(prompts));
- filterPrompts(); // 重新过滤列表
- };
-
- // 添加新提示词
- document.getElementById('pm-add').addEventListener('click', () => {
- currentEditIndex = null;
- document.getElementById('pm-title').value = '';
- document.getElementById('pm-content').value = '';
- document.getElementById('pm-modal').style.display = 'flex';
- });
-
- // 关闭模态框
- document.getElementById('pm-close').addEventListener('click', () => {
- document.getElementById('pm-modal').style.display = 'none';
- });
-
- // 取消操作
- document.getElementById('pm-cancel').addEventListener('click', () => {
- document.getElementById('pm-modal').style.display = 'none';
- });
-
- // 保存提示词
- document.getElementById('pm-save').addEventListener('click', () => {
- const title = document.getElementById('pm-title').value.trim();
- const content = document.getElementById('pm-content').value.trim();
-
- if (!title || !content) {
- alert('标题和内容不能为空');
- return;
- }
-
- if (currentEditIndex !== null) {
- prompts[currentEditIndex] = { title, content };
- } else {
- prompts.push({ title, content });
- }
-
- localStorage.setItem(STORAGE_KEY, JSON.stringify(prompts));
- filterPrompts(); // 刷新列表
- document.getElementById('pm-modal').style.display = 'none';
- });
-
- // 最小化功能
- document.getElementById('pm-minimize').addEventListener('click', (e) => {
- e.stopPropagation();
- isMinimized = !isMinimized;
- container.classList.toggle('minimized', isMinimized);
- });
-
- container.addEventListener('click', () => {
- if (isMinimized) {
- isMinimized = false;
- container.classList.remove('minimized');
- }
- });
-
- // 导出功能
- document.getElementById('pm-export').addEventListener('click', () => {
- const json = JSON.stringify(prompts);
- const blob = new Blob([json], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = 'prompts.json';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- });
-
- // 导入功能
- document.getElementById('pm-import').addEventListener('click', () => {
- document.getElementById('pm-import-file').click();
- });
-
- document.getElementById('pm-import-file').addEventListener('change', (event) => {
- const file = event.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = (e) => {
- try {
- const json = JSON.parse(e.target.result);
- if (Array.isArray(json)) {
- prompts = json;
- localStorage.setItem(STORAGE_KEY, JSON.stringify(prompts));
- filterPrompts(); // 重新渲染列表
- alert('导入成功');
- } else {
- alert('文件格式不正确,应为JSON数组');
- }
- } catch (error) {
- alert('文件解析失败:' + error);
- }
- };
- reader.readAsText(file);
- }
- });
-
- // 搜索功能
- const searchInput = document.getElementById('pm-search');
- searchInput.addEventListener('input', () => {
- filterPrompts(searchInput.value.trim());
- });
-
- // 过滤提示词
- function filterPrompts(searchTerm = '') {
- if (searchTerm) {
- const lowerSearchTerm = searchTerm.toLowerCase();
- filteredPrompts = prompts.filter(item =>
- item.title.toLowerCase().includes(lowerSearchTerm) ||
- item.content.toLowerCase().includes(lowerSearchTerm)
- );
- } else {
- filteredPrompts = [...prompts]; // 恢复到所有提示词
- }
- renderList();
- }
-
- // 初始化
- filterPrompts();
- })();