您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add prompt selector for bolt.new
// ==UserScript== // @name Bolt Prompt Selector // @namespace http://tampermonkey.net/ // @version 0.1 // @description Add prompt selector for bolt.new // @author Your name // @match https://bolt.new/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; function init() { // 清理已存在的元素 const cleanup = () => { const existingTooltip = document.querySelector('.prompt-tooltip'); if (existingTooltip) { existingTooltip.remove(); } }; cleanup(); // 检查是否已注入 if (document.querySelector('.prompt-container')) { return; } // 检查必要的DOM元素 const targetElement = document.querySelector('.z-prompt'); if (!targetElement) { console.log('Target element not found, waiting...'); return; } const textarea = document.querySelector('textarea.w-full'); if (!textarea) { console.log('Textarea not found, waiting...'); return; } const promptList = [ { title: "DRY原则和数据视图分离", prompt: "遵循DRY(Don't Repeat Yourself)原则,将数据逻辑与视图逻辑分离。使用专门的数据层处理状态和业务逻辑,通过props或context传递给视图组件。", group: "Architecture" }, { title: "类型定义优先", prompt: "在实现具体功能前,优先定义完整的TypeScript类型和接口。包括API响应类型、组件Props类型、状态类型等。", group: "Architecture" }, { title: "模块化开发", prompt: "将代码按功能模块化,每个模块应该是独立的、可测试的单元。", group: "Architecture" }, { title: "Shadcn UI优先", prompt: "优先使用Shadcn UI组件库构建界面。避免重复造轮子。", group: "UI" }, { title: "响应式设计规范", prompt: "使用Tailwind的响应式前缀(sm:、md:、lg:)构建响应式布局。移动端优先,确保在各种设备上都有良好的显示效果。", group: "UI" }, { title: "主题定制规范", prompt: "在globals.css中集中管理主题变量。使用Shadcn UI的主题系统,通过CSS变量统一管理颜色、字体等样式。", group: "UI" }, { title: "表单字段验证", prompt: "使用Zod进行表单验证,为每个表单创建专门的验证schema。必填字段使用.required(),可选字段使用.optional()。错误信息应该明确友好。", group: "Form" }, { title: "表单布局规范", prompt: "表单采用紧凑(compact)布局,简单input字段同行显示,最多两列。较复杂的字段独占一行。", group: "Form" }, { title: "表单状态管理", prompt: "使用React Hook Form管理表单状态。实现实时验证、提交处理、错误展示等完整表单生命周期。", group: "Form" }, { title: "Server Components优先", prompt: "优先使用Server Components处理数据获取。合理使用use server和use client指令。避免不必要的客户端渲染。", group: "Data" }, { title: "缓存策略", prompt: "根据数据特点设置合适的缓存策略。使用Next.js的缓存API。实现适当的重验证机制。", group: "Data" }, { title: "元数据管理", prompt: "使用Next.js的Metadata API管理SEO元数据。在layout.tsx和page.tsx中设置动态metadata。确保title、description等关键标签完整。", group: "SEO" }, { title: "结构化数据", prompt: "实现JSON-LD结构化数据。针对不同类型的页面(文章、商品、评论等)使用对应的Schema标记。验证结构化数据的正确性。", group: "SEO" }, { title: "语义化HTML", prompt: "使用语义化的HTML标签构建页面结构。合理使用heading层级(h1-h6)。使用article、section、nav等语义化标签标识内容区域。", group: "SEO" }, { title: "图片优化SEO", prompt: "图片添加有意义的alt文本。使用Next.js的Image组件自动优化图片。考虑使用srcset提供响应式图片。添加图片的width和height避免布局偏移。", group: "SEO" }, { title: "性能优化SEO", prompt: "确保核心Web指标(Core Web Vitals)达标。包括LCP、FID、CLS等指标的优化。使用next/dynamic实现代码分割。", group: "SEO" }, { title: "国际化SEO", prompt: "使用i18next实现多语言支持,语言文件存储在app/i18n/locales文件夹中。添加合适的hreflang标签。考虑内容的本地化需求。", group: "SEO" }, { title: "统一错误处理", prompt: "实现统一的错误边界处理组件。API调用使用try-catch包装,展示友好的错误提示。记录错误日志便于追踪。", group: "Error" }, { title: "加载状态处理", prompt: "实现统一的加载状态组件。使用Suspense和loading.tsx处理路由切换。接口调用时展示适当的加载提示。", group: "Error" }, { title: "图片优化", prompt: "使用Next.js的Image组件处理图片。设置合适的宽高、质量参数。使用BlurHash等技术优化加载体验。", group: "Performance" }, { title: "状态管理优化", prompt: "合理使用React状态管理。局部状态用useState,跨组件状态用Context,复杂状态考虑使用Zustand等轻量级方案。", group: "Performance" }, { title: "Code Splitting", prompt: "使用Next.js的动态导入功能(dynamic import)实现代码分割。路由级别的组件默认分割,大型组件或库考虑按需加载。", group: "Performance" } ].map(item => ({ ...item, selected: false })); // 创建容器 const container = document.createElement('div'); container.classList.add('prompt-container'); container.style.cssText = 'width:502px;max-height:300px;overflow-y:auto;background:#1a1a1a;padding:8px;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.2);font-family:system-ui,-apple-system;scrollbar-width:thin;scrollbar-color:#4a4a4a #1a1a1a;opacity:0;transform:translateY(-10px);transition:all 0.2s ease;display:none;'; container.onscroll = (e) => e.stopPropagation(); // 创建tooltip const tooltip = document.createElement('div'); tooltip.classList.add('prompt-tooltip'); tooltip.style.cssText = ` position: fixed; padding: 8px 12px; background: #2d2d2d; border: 1px solid #404040; border-radius: 6px; font-size: 13px; color: #e5e7eb; max-width: 300px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); pointer-events: none; opacity: 0; transform: translateY(4px); transition: opacity 0.2s, transform 0.2s; z-index: 9999; line-height: 1.5; font-family: system-ui, -apple-system; word-break: break-word; `; document.body.appendChild(tooltip); // 辅助函数:设置textarea的值 const setTextareaValue = (textarea, value) => { try { const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set; nativeInputValueSetter.call(textarea, value); const event = new Event('input', { bubbles: true }); textarea.dispatchEvent(event); } catch (error) { console.error('Error setting textarea value:', error); } }; // 按组分类 const groups = promptList.reduce((acc, item) => { if (!acc[item.group]) { acc[item.group] = []; } acc[item.group].push(item); return acc; }, {}); // 渲染按钮 const renderButtons = () => { try { Object.entries(groups).forEach(([groupName, items]) => { const groupDiv = document.createElement('div'); groupDiv.style.cssText = 'margin:4px 0;'; const groupLabel = document.createElement('div'); groupLabel.textContent = groupName; groupLabel.style.cssText = 'color:#6b7280;font-size:12px;padding:0 4px;margin-bottom:2px;'; groupDiv.appendChild(groupLabel); const buttonsContainer = document.createElement('div'); buttonsContainer.style.cssText = 'display:grid;grid-template-columns:repeat(2,1fr);gap:4px;'; items.forEach(item => { const button = document.createElement('button'); button.textContent = item.title; const getButtonStyles = (isSelected, isHovered = false) => ` display:block; width:100%; padding:6px 10px; border:1px solid ${isSelected ? '#4CAF50' : '#333'}; border-radius:4px; background:${isSelected ? '#1b4a1f' : isHovered ? '#2f2f2f' : '#242424'}; color:${isSelected ? '#4CAF50' : '#e5e7eb'}; font-size:13px; text-align:left; cursor:${isSelected ? 'not-allowed' : 'pointer'}; transition:all 0.2s; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; opacity:${isSelected ? '0.8' : '1'}; `; button.style.cssText = getButtonStyles(item.selected); // Hover events for tooltip button.addEventListener('mouseenter', (e) => { const rect = e.target.getBoundingClientRect(); tooltip.textContent = item.prompt; tooltip.style.opacity = '1'; tooltip.style.transform = 'translateY(0)'; // Calculate best position const tooltipRect = tooltip.getBoundingClientRect(); const spaceBelow = window.innerHeight - rect.bottom; const spaceAbove = rect.top; if (spaceBelow >= tooltipRect.height + 8) { tooltip.style.top = `${rect.bottom + 8}px`; } else if (spaceAbove >= tooltipRect.height + 8) { tooltip.style.top = `${rect.top - tooltipRect.height - 8}px`; } else { tooltip.style.top = `${window.innerHeight/2 - tooltipRect.height/2}px`; } let left = rect.left; if (left + tooltipRect.width > window.innerWidth - 20) { left = window.innerWidth - tooltipRect.width - 20; } tooltip.style.left = `${Math.max(20, left)}px`; }); button.addEventListener('mouseleave', () => { tooltip.style.opacity = '0'; tooltip.style.transform = 'translateY(4px)'; }); button.onmouseover = () => !item.selected && (button.style.cssText = getButtonStyles(item.selected, true)); button.onmouseout = () => !item.selected && (button.style.cssText = getButtonStyles(item.selected)); button.onclick = (e) => { if (item.selected) return; e.stopPropagation(); const textarea = document.querySelector('textarea.w-full'); if (textarea) { const currentValue = textarea.value; setTextareaValue(textarea, currentValue + (currentValue && currentValue.trim() ? ' ' : '') + item.prompt); item.selected = true; button.style.cssText = getButtonStyles(true); } }; buttonsContainer.appendChild(button); }); groupDiv.appendChild(buttonsContainer); container.appendChild(groupDiv); }); } catch (error) { console.error('Error rendering buttons:', error); } }; // 初始渲染 renderButtons(); // 插入容器 targetElement.insertBefore(container, targetElement.firstChild); // 显示/隐藏容器的函数 const showContainer = () => { container.style.display = 'block'; setTimeout(() => { container.style.opacity = '1'; container.style.transform = 'translateY(0)'; }, 10); }; const hideContainer = () => { promptList.forEach(item => item.selected = false); container.innerHTML = ''; renderButtons(); container.style.opacity = '0'; container.style.transform = 'translateY(-10px)'; tooltip.style.opacity = '0'; tooltip.style.transform = 'translateY(4px)'; setTimeout(() => { container.style.display = 'none'; }, 200); }; // 事件监听 textarea.addEventListener('focus', showContainer); document.addEventListener('click', (e) => { if (!container.contains(e.target) && e.target !== textarea) { hideContainer(); } }); } // 使用MutationObserver监听DOM变化 const observer = new MutationObserver((mutations, obs) => { const targetElement = document.querySelector('.z-prompt'); if (targetElement) { init(); obs.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); })();