// ==UserScript== // @name My Prompt // @name:pt-BR Meu Prompt // @name:zh-CN 我的提示 // @name:zh-TW 我的提示詞 // @name:en My Prompt // @name:es Mi Prompt // @name:ja 私のプロンプト // @name:ko 나의 프롬프트 // @name:de Mein Prompt // @name:fr Mon Prompt // @namespace https://github.com/0H4S // @version 1.8 // @description Save and use your prompts quickly and easily with just one click! Compatible with ChatGPT, DeepSeek, Gemini, Claude, Kimi, Qwen, LMArena, Z.ai, and Google AI Studio. // @description:pt-BR Salve e use seus prompts de forma rápida e fácil com apenas um clique! Compatível com ChatGPT, DeepSeek, Gemini, Claude, Kimi, Qwen, LMArena, Z.ai e Google AI Studio. // @description:zh-CN 只需一键即可快速轻松地保存和使用您的提示!兼容 ChatGPT、DeepSeek、Gemini、Claude、Kimi、Qwen、LMArena、Z.ai 和 Google AI Studio。 // @description:zh-TW 只需一鍵即可快速輕鬆地儲存並使用您的提示!相容於 ChatGPT、DeepSeek、Gemini、Claude、Kimi、Qwen、LMArena、Z.ai 和 Google AI Studio。 // @description:en Save and use your prompts quickly and easily with just one click! Compatible with ChatGPT, DeepSeek, Gemini, Claude, Kimi, Qwen, LMArena, Z.ai, and Google AI Studio. // @description:es ¡Guarda y usa tus prompts de forma rápida y sencilla con un solo clic! Compatible con ChatGPT, DeepSeek, Gemini, Claude, Kimi, Qwen, LMArena, Z.ai y Google AI Studio. // @description:ja ワンクリックで簡単かつスピーディーにプロンプトを保存・使用!ChatGPT、DeepSeek、Gemini、Claude、Kimi、Qwen、LMArena、Z.ai、Google AI Studio に対応しています。 // @description:ko 클릭 한 번으로 프롬프트를 빠르고 쉽게 저장하고 사용할 수 있습니다! ChatGPT, DeepSeek, Gemini, Claude, Kimi, Qwen, LMArena, Z.ai 및 Google AI Studio와 호환됩니다. // @description:de Speichern und verwenden Sie Ihre Prompts schnell und einfach mit nur einem Klick! Kompatibel mit ChatGPT, DeepSeek, Gemini, Claude, Kimi, Qwen, LMArena, Z.ai und Google AI Studio. // @description:fr Enregistrez et utilisez vos prompts rapidement et facilement en un seul clic ! Compatible avec ChatGPT, DeepSeek, Gemini, Claude, Kimi, Qwen, LMArena, Z.ai et Google AI Studio. // @author OHAS // @homepage https://github.com/0H4S // @icon https://cdn-icons-png.flaticon.com/512/4997/4997543.png // @license CC-BY-NC-ND-4.0 // @copyright 2025 OHAS. All Rights Reserved. // @match https://aistudio.google.com/* // @match https://gemini.google.com/* // @match https://chat.deepseek.com/* // @match https://www.kimi.com/* // @match https://chat.qwen.ai/* // @match https://chatgpt.com/* // @match https://lmarena.ai/* // @match https://chat.z.ai/* // @match https://claude.ai/* // @require https://update.greasyfork.icu/scripts/549920.js // @connect gist.github.com // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @run-at document-end // @noframes // @compatible chrome // @compatible firefox // @compatible edge // @compatible opera // @bgf-colorLT #847dfd // @bgf-colorDT #6963ca // @bgf-compatible brave // @bgf-copyright [2025 OHAS. All Rights Reserved.](https://gist.github.com/0H4S/ae2fa82957a089576367e364cbf02438) // @bgf-social https://github.com/0H4S // @contributionURL https://linktr.ee/0H4S // @downloadURL none // ==/UserScript== (function() { 'use strict'; // #region GLOBAL const translations = { 'en': { langName: 'English', prompt: 'Prompt', prompts: 'Prompts', newPrompt: 'New Prompt', editPrompt: 'Edit Prompt', title: 'Title', text: 'Prompt', save: 'Save', close: 'Close', edit: 'Edit', delete: 'Delete', noSavedPrompts: 'No saved prompts.', addPrompt: 'Add prompt', import: 'Import', export: 'Export', confirmDelete: 'Delete prompt "{title}"?', noPromptsToExport: 'No prompts to export.', promptsImported: '{count} prompts imported successfully!', errorImporting: 'Error importing file: {error}', requiredFields: 'Title and prompt are required.', editorNotFound: 'Could not find the text area for {platform}.', languageSettings: '🌐 Language', fillPlaceholders: 'Fill in the Information', insert: 'Insert', enablePlaceholders: 'Enable interactive information: [...]', fileName: 'My_Prompts.json' }, 'pt-BR': { langName: 'Português (BR)', prompt: 'Prompt', prompts: 'Prompts', newPrompt: 'Novo Prompt', editPrompt: 'Editar Prompt', title: 'Título', text: 'Prompt', save: 'Salvar', close: 'Fechar', edit: 'Editar', delete: 'Excluir', noSavedPrompts: 'Nenhum prompt salvo.', addPrompt: 'Adicionar prompt', import: 'Importar', export: 'Exportar', confirmDelete: 'Excluir prompt "{title}"?', noPromptsToExport: 'Não há prompts para exportar.', promptsImported: '{count} prompts importados com sucesso!', errorImporting: 'Erro ao importar o arquivo: {error}', requiredFields: 'Título e prompt são obrigatórios.', editorNotFound: 'Não foi possível encontrar a área de texto para {platform}.', languageSettings: '🌐 Idioma', fillPlaceholders: 'Preencha as Informações', insert: 'Inserir', enablePlaceholders: 'Ativar informações interativas: [...]', fileName: 'Meus_Prompts.json' }, 'es': { langName: 'Español', prompt: 'Prompt', prompts: 'Prompts', newPrompt: 'Nuevo Prompt', editPrompt: 'Editar Prompt', title: 'Título', text: 'Prompt', save: 'Guardar', close: 'Cerrar', edit: 'Editar', delete: 'Eliminar', noSavedPrompts: 'No hay prompts guardados.', addPrompt: 'Añadir prompt', import: 'Importar', export: 'Exportar', confirmDelete: '¿Eliminar prompt "{title}"?', noPromptsToExport: 'No hay prompts para exportar.', promptsImported: '¡{count} prompts importados con éxito!', errorImporting: 'Error al importar el archivo: {error}', requiredFields: 'El título y el prompt son obligatorios.', editorNotFound: 'No se pudo encontrar el área de texto para {platform}.', languageSettings: '🌐 Idioma', fillPlaceholders: 'Rellene la Información', insert: 'Insertar', enablePlaceholders: 'Activar información interactiva: [...]', fileName: 'Mis_Prompts.json' }, 'zh-CN': { langName: '简体中文', prompt: '提示', prompts: '提示', newPrompt: '新建提示', editPrompt: '编辑提示', title: '标题', text: '提示内容', save: '保存', close: '关闭', edit: '编辑', delete: '删除', noSavedPrompts: '没有已保存的提示。', addPrompt: '添加提示', import: '导入', export: '导出', confirmDelete: '确定要删除提示 "{title}" 吗?', noPromptsToExport: '沒有可導出的提示。', promptsImported: '成功导入 {count} 个提示!', errorImporting: '导入文件时出错: {error}', requiredFields: '标题和提示内容为必填项。', editorNotFound: '未能找到 {platform} 的文本输入区域。', languageSettings: '🌐 语言', fillPlaceholders: '填写信息', insert: '插入', enablePlaceholders: '启用交互式信息: [...]', fileName: '我的提示.json' }, 'ja': { langName: '日本語', prompt: 'プロンプト', prompts: 'プロンプト', newPrompt: '新規プロンプト', editPrompt: 'プロンプト編集', title: 'タイトル', text: 'プロンプト', save: '保存', close: '閉じる', edit: '編集', delete: '削除', noSavedPrompts: '保存されたプロンプトはありません', addPrompt: 'プロンプトを追加', import: 'インポート', export: 'エクスポート', confirmDelete: 'プロンプト「{title}」を削除しますか?', noPromptsToExport: 'エクスポートするプロンプトがありません', promptsImported: '{count}件のプロンプトをインポートしました', errorImporting: 'ファイルのインポート中にエラーが発生しました: {error}', requiredFields: 'タイトルとプロンプトは必須です', editorNotFound: '{platform}のテキストエリアが見つかりません', languageSettings: '🌐 言語設定', fillPlaceholders: '情報を入力', insert: '挿入', enablePlaceholders: 'インタラクティブな情報を有効にする: [...]', fileName: 'マイプロンプト.json' }, 'ko': { langName: '한국어', prompt: '프롬프트', prompts: '프롬프트', newPrompt: '새 프롬프트', editPrompt: '프롬프트 편집', title: '제목', text: '프롬프트', save: '저장', close: '닫기', edit: '편집', delete: '삭제', noSavedPrompts: '저장된 프롬프트가 없습니다', addPrompt: '프롬프트 추가', import: '가져오기', export: '내보내기', confirmDelete: '프롬프트 "{title}"을(를) 삭제하시겠습니까?', noPromptsToExport: '내보낼 프롬프트가 없습니다', promptsImported: '프롬프트 {count}개를 성공적으로 가져왔습니다', errorImporting: '파일 가져오기 오류: {error}', requiredFields: '제목과 프롬프트는 필수 항목입니다', editorNotFound: '{platform}의 텍스트 영역을 찾을 수 없습니다', languageSettings: '🌐 언어 설정', fillPlaceholders: '정보 채우기', insert: '삽입', enablePlaceholders: '대화형 정보 활성화: [...]', fileName: '내_프롬프트.json' } }; const LANG_STORAGE_KEY = 'UserScriptLang'; let currentLang = 'en'; const SCRIPT_CONFIG = { notificationsUrl: 'https://gist.github.com/0H4S/b2f9a9f92259deadc35bdccb11cd9a75', scriptVersion: '1.8', }; const notifier = new ScriptNotifier(SCRIPT_CONFIG); notifier.run(); const PROMPT_STORAGE_KEY = 'Prompts'; let isInitialized = false; let isInitializing = false; let currentButton = null; let currentPlatform = null; let pageObserver = null; let currentMenu = null; let currentModal = null; let languageModal = null; let currentPlaceholderModal = null; const scriptPolicy = window.trustedTypes ? window.trustedTypes.createPolicy('MyPromptPolicy', { createHTML: (input) => input }) : null; function setSafeInnerHTML(element, html) { if (!element) return; if (scriptPolicy) { element.innerHTML = scriptPolicy.createHTML(html); } else { element.innerHTML = html; } } function getTranslation(key, replacements = {}) { let text = translations[currentLang]?.[key] || translations.en[key]; Object.entries(replacements).forEach(([p, v]) => text = text.replace(`{${p}}`, v)); return text; } async function determineLanguage() { const savedLang = await GM_getValue(LANG_STORAGE_KEY); if (savedLang && translations[savedLang]) { currentLang = savedLang; return; } const browserLang = (navigator.language || navigator.userLanguage).toLowerCase(); if (browserLang.startsWith('pt')) currentLang = 'pt-BR'; else if (browserLang.startsWith('zh')) currentLang = 'zh-CN'; else if (browserLang.startsWith('en')) currentLang = 'en'; else if (browserLang.startsWith('es')) currentLang = 'es'; else if (browserLang.startsWith('ja')) currentLang = 'ja'; else if (browserLang.startsWith('ko')) currentLang = 'ko'; else currentLang = 'en'; } function waitFor(selector, timeout = 8000) { return new Promise((resolve, reject) => { const el = document.querySelector(selector); if (el) { resolve(el); return; } const timer = setTimeout(() => { obs.disconnect(); reject(`Timeout esperando por ${selector}`); }, timeout); const obs = new MutationObserver(() => { const target = document.querySelector(selector); if (target) { clearTimeout(timer); obs.disconnect(); resolve(target); } }); if (document.body) obs.observe(document.body, { childList: true, subtree: true }); else document.addEventListener('DOMContentLoaded', () => obs.observe(document.body, { childList: true, subtree: true })); }); } const debounce = (func, wait) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; }; async function getAll() { return await GM_getValue(PROMPT_STORAGE_KEY, []); } async function addItem(item) { const prompts = await getAll(); prompts.unshift(item); await GM_setValue(PROMPT_STORAGE_KEY, prompts); } async function update(index, item) { let prompts = await getAll(); if (prompts[index]) { prompts[index] = item; await GM_setValue(PROMPT_STORAGE_KEY, prompts); } } async function remove(index) { let prompts = await getAll(); prompts.splice(index, 1); await GM_setValue(PROMPT_STORAGE_KEY, prompts); } function createCustomTooltip(button, text, position = 'top') { let tooltipElement = null; const showTooltip = () => { if (tooltipElement) return; tooltipElement = document.createElement('div'); tooltipElement.className = 'mp-tooltip'; tooltipElement.textContent = text; document.body.appendChild(tooltipElement); const btnRect = button.getBoundingClientRect(); const tooltipRect = tooltipElement.getBoundingClientRect(); let top; const margin = 8; if (position === 'bottom') { top = btnRect.bottom + margin; if (top + tooltipRect.height > window.innerHeight) { top = btnRect.top - tooltipRect.height - margin; } } else { top = btnRect.top - tooltipRect.height - margin; if (top < 0) { top = btnRect.bottom + margin; } } let left = btnRect.left + (btnRect.width / 2) - (tooltipRect.width / 2); if (left < 0) { left = margin; } if (left + tooltipRect.width > window.innerWidth) { left = window.innerWidth - tooltipRect.width - margin; } tooltipElement.style.left = `${left}px`; tooltipElement.style.top = `${top}px`; requestAnimationFrame(() => { tooltipElement.classList.add('visible'); }); }; const hideTooltip = () => { if (!tooltipElement) return; const el = tooltipElement; tooltipElement = null; el.classList.remove('visible'); setTimeout(() => { if (document.body.contains(el)) { document.body.removeChild(el); } }, 150); }; button.addEventListener('mouseenter', showTooltip); button.addEventListener('mouseleave', hideTooltip); button.addEventListener('mousedown', hideTooltip); } function injectGlobalStyles() { const styleId='my-prompt-styles'; if (document.getElementById(styleId)) return; const styleElement=document.createElement('style'); styleElement.id=styleId; setSafeInnerHTML(styleElement, ` @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); :root { --mp-font-family-base: 'Inter', sans-serif; --mp-bg-primary: #fff; --mp-bg-secondary: #f8f9fa; --mp-bg-tertiary: #f1f3f5; --mp-bg-overlay: rgba(10, 10, 10, .5); --mp-text-primary: #212529; --mp-text-secondary: #495057; --mp-text-tertiary: #868e96; --mp-border-primary: #dee2e6; --mp-border-secondary: #ced4da; --mp-accent-primary: #7071fc; --mp-accent-primary-hover: #595ac9; --mp-accent-yellow: #fab005; --mp-accent-yellow-hover: #f08c00; --mp-accent-red: #f03e3e; --mp-accent-red-hover: #c92a2a; --mp-shadow-sm: 0 1px 2px rgba(0, 0, 0, .04); --mp-shadow-md: 0 4px 12px rgba(0, 0, 0, .1); --mp-shadow-lg: 0 10px 30px rgba(0, 0, 0, .1); --mp-border-radius-sm: 4px; --mp-border-radius-md: 8px; --mp-border-radius-lg: 16px; --mp-transition-fast: .2s cubic-bezier(.25, 1, .5, 1) } @media (prefers-color-scheme:dark) { :root { --mp-bg-primary: #212529; --mp-bg-secondary: #2c2c30; --mp-bg-tertiary: #343a40; --mp-bg-overlay: rgba(0, 0, 0, .6); --mp-text-primary: #f8f9fa; --mp-text-secondary: #e9ecef; --mp-text-tertiary: #adb5bd; --mp-border-primary: #495057; --mp-border-secondary: #868e96; --mp-shadow-sm: 0 1px 2px rgba(0, 0, 0, .15); --mp-shadow-md: 0 4px 12px rgba(0, 0, 0, .25); --mp-shadow-lg: 0 10px 30px rgba(0, 0, 0, .3) } } .mp-hidden { display: none !important; } .mp-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--mp-bg-overlay); z-index: 2147483647; display: flex; justify-content: center; align-items: center; backdrop-filter: blur(4px); opacity: 0; visibility: hidden; transition: opacity var(--mp-transition-fast), visibility var(--mp-transition-fast); } .mp-overlay.visible { opacity: 1; visibility: visible; } .mp-modal-box { font-family: var(--mp-font-family-base); background-color: var(--mp-bg-primary); color: var(--mp-text-primary); border-radius: var(--mp-border-radius-lg); padding: 24px; box-shadow: var(--mp-shadow-lg); width: min(90vw, 520px); border: 1px solid var(--mp-border-primary); transform: scale(.95) translateY(10px); opacity: 0; transition: transform var(--mp-transition-fast), opacity var(--mp-transition-fast); position: relative; } .mp-overlay.visible .mp-modal-box { transform: scale(1) translateY(0); opacity: 1; } .mp-modal-close-btn { position: absolute; top: 12px; right: 12px; background: none; border: none; color: var(--mp-text-tertiary); font-size: 22px; cursor: pointer; width: 32px; height: 32px; border-radius: 50%; transition: transform .3s ease, color .3s ease, background-color .3s ease; display: flex; justify-content: center; align-items: center; line-height: 1; } .mp-modal-close-btn:hover { transform: rotate(90deg); color: var(--mp-accent-red); background-color: color-mix(in srgb, var(--mp-accent-red) 15%, transparent); } .prompt-menu { position: fixed; min-width: 320px; max-width: 420px; background-color: var(--mp-bg-primary); border: 1px solid var(--mp-border-primary); border-radius: var(--mp-border-radius-lg); box-shadow: var(--mp-shadow-lg); z-index: 2147483647; display: flex; flex-direction: column; user-select: none; color: var(--mp-text-primary); font-family: var(--mp-font-family-base); overflow: hidden; opacity: 0; visibility: hidden; transform: scale(.95); transform-origin: top left; transition: opacity .2s ease, transform .2s ease, visibility 0s linear .2s; } .prompt-menu.visible { opacity: 1; visibility: visible; transform: scale(1); transition-delay: 0s; } .prompt-menu-list { max-height: 220px; overflow-y: auto; padding: 4px; } .prompt-item-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-radius: var(--mp-border-radius-md); cursor: pointer; transition: background-color .15s ease-in-out; } .prompt-item-row:hover { background-color: var(--mp-bg-tertiary); } .prompt-title { font-size: 14px; font-weight: 500; flex: 1; padding-right: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--mp-text-secondary); } .prompt-item-row:hover .prompt-title { color: var(--mp-text-primary); } .prompt-actions { display: flex; align-items: center; gap: 4px; } .action-btn { background: 0 0; border: none; cursor: pointer; font-size: 13px; font-weight: 500; padding: 4px 8px; border-radius: var(--mp-border-radius-sm); transition: background-color .15s ease-in-out, color .15s ease-in-out; font-family: var(--mp-font-family-base); } .action-btn.edit { color: var(--mp-accent-yellow); } .action-btn.edit:hover { background-color: var(--mp-accent-yellow); color: var(--mp-bg-primary); } .action-btn.delete { color: var(--mp-accent-red); } .action-btn.delete:hover { background-color: var(--mp-accent-red); color: var(--mp-bg-primary); } .menu-footer, .menu-section { border-top: 1px solid var(--mp-border-primary); padding: 4px; } .menu-button { display: flex; align-items: center; justify-content: center; padding: 8px 12px; cursor: pointer; transition: background-color .15s ease-in-out; color: var(--mp-text-secondary); border-radius: var(--mp-border-radius-md); font-size: 14px; font-weight: 500; } .menu-button:hover { background-color: var(--mp-bg-tertiary); color: var(--mp-text-primary); } .menu-button svg { margin-right: 8px; } .import-export-container { display: flex; } .import-export-container .menu-button { flex: 1; } .divider { border-left: 1px solid var(--mp-border-primary); height: 24px; align-self: center; } .empty-state { padding: 24px 16px; text-align: center; color: var(--mp-text-tertiary); font-size: 14px; } .form-group { display: flex; flex-direction: column; margin-bottom: 16px; } .form-label { margin-bottom: 6px; font-size: 13px; font-weight: 500; color: var(--mp-text-secondary); } .form-input { background-color: var(--mp-bg-secondary); color: var(--mp-text-primary); border: 1px solid var(--mp-border-primary) !important; border-radius: var(--mp-border-radius-md); padding: 10px; width: 100%; box-sizing: border-box; transition: border-color .2s, box-shadow .2s; outline: 0 !important; font-family: var(--mp-font-family-base); font-size: 14px } .form-textarea { background-color: var(--mp-bg-secondary); color: var(--mp-text-primary); border: 1px solid var(--mp-border-primary) !important; border-radius: var(--mp-border-radius-md); padding: 10px; width: 100%; box-sizing: border-box; outline: 0 !important; font-family: var(--mp-font-family-base); font-size: 14px; height: 140px; resize: vertical; transition: border-color .2s, box-shadow .2s; } .form-input:focus, .form-textarea:focus { border-color: var(--mp-accent-primary) !important; box-shadow: 0 0 0 3px color-mix(in srgb, var(--mp-accent-primary) 25%, transparent) !important; } .modal-title { font-size: 18px; font-weight: 600; margin: 0 0 24px; text-align: center; color: var(--mp-text-primary); } .modal-footer { display: flex; justify-content: flex-end; } .save-button { padding: 10px 28px; border-radius: var(--mp-border-radius-md); background-color: var(--mp-accent-primary); color: #fff; border: none; font-weight: 600; cursor: pointer; transition: all .2s ease-in-out; font-family: var(--mp-font-family-base); } .save-button:hover { background-color: var(--mp-accent-primary-hover); transform: translateY(-1px); } .lang-box { width: min(90vw, 320px); } .lang-buttons-container { display: flex; flex-direction: column; gap: 12px; } #__ap_placeholders_container { max-height: 350px; overflow-y: auto; padding-right: 12px; margin-right: -12px; scrollbar-width: thin; scrollbar-color: var(--mp-border-secondary) var(--mp-bg-tertiary); } #__ap_placeholders_container::-webkit-scrollbar { width: 10px !important; height: 10px !important; } #__ap_placeholders_container::-webkit-scrollbar-track { background-color: var(--mp-bg-tertiary) !important; border-radius: 10px !important; border: none !important; } #__ap_placeholders_container::-webkit-scrollbar-thumb { background-color: var(--mp-border-secondary) !important; border-radius: 10px !important; border: 2px solid var(--mp-bg-primary) !important; } #__ap_placeholders_container::-webkit-scrollbar-thumb:hover { background-color: var(--mp-text-tertiary) !important; } .form-checkbox-group { display: flex; align-items: center; gap: 10px; margin-top: -8px; margin-bottom: 20px; } .form-checkbox-group label { font-size: 13px; font-weight: 500; color: var(--mp-text-secondary); cursor: pointer; margin-bottom: 0; } .form-checkbox-group input[type="checkbox"] { cursor: pointer; width: 16px; height: 16px; accent-color: var(--mp-accent-primary); } @keyframes mp-fade-in-up { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .lang-button { all: unset; box-sizing: border-box; display: block; width: 100%; padding: 12px 20px; border-radius: var(--mp-border-radius-md); background-color: var(--mp-bg-secondary); color: var(--mp-text-primary); border: 1px solid var(--mp-border-primary); font-weight: 500; cursor: pointer; text-align: center; opacity: 0; animation: mp-fade-in-up .4s ease forwards; transition: transform .2s ease, box-shadow .2s ease, background-color .2s ease; font-family: var(--mp-font-family-base); } .lang-button:hover { transform: translateY(-3px); box-shadow: var(--mp-shadow-md); background-color: var(--mp-bg-tertiary); } .lang-button:active { transform: translateY(0); transition-duration: .1s; } .prompt-menu-list { scrollbar-width: thin; scrollbar-color: var(--mp-border-secondary) var(--mp-bg-tertiary); } .prompt-menu-list::-webkit-scrollbar { width: 10px !important; height: 10px !important; } .prompt-menu-list::-webkit-scrollbar-track { background-color: var(--mp-bg-tertiary) !important; border-radius: 10px !important; border: none !important; } .prompt-menu-list::-webkit-scrollbar-thumb { background-color: var(--mp-border-secondary) !important; border-radius: 10px !important; border: 2px solid var(--mp-bg-primary) !important; } .prompt-menu-list::-webkit-scrollbar-thumb:hover { background-color: var(--mp-text-tertiary) !important; } .mp-tooltip { position: fixed; z-index: 2147483647; border-radius: var(--mp-border-radius-sm); padding: 6px 12px; pointer-events: none; white-space: nowrap; font-family: var(--mp-font-family-base); font-size: 14px; font-weight: 500; background-color: var(--mp-text-primary); color: var(--mp-bg-primary); box-shadow: var(--mp-shadow-md); border: 1px solid var(--mp-bg-tertiary); opacity: 0; transform: scale(0.95); transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1); } .mp-tooltip.visible { opacity: 1; transform: scale(1); } `); document.head.appendChild(styleElement); } // #endregion // #region CRIAR BOTÕES // CHATGPT function createChatGPTButton() { const btn = document.createElement('button'); btn.type = 'button'; btn.setAttribute('data-testid', 'composer-button-prompts'); btn.className = 'composer-btn'; setSafeInnerHTML(btn, ``); createCustomTooltip(btn, getTranslation('prompts')); return btn; } // DEEPSEEK function createDeepSeekButton() { const btn = document.createElement('button'); btn.setAttribute('data-testid', 'composer-button-prompts'); setSafeInnerHTML(btn, `
${getTranslation('prompt')}`); return btn; } // GOOGLE AI STUDIO function createGoogleStudioButton() { const styleId = 'my-prompt-gstudio-hover-fix'; if (!document.getElementById(styleId)) { const styleElement = document.createElement('style'); styleElement.id = styleId; setSafeInnerHTML(styleElement, ` button[data-testid="composer-button-prompts"]:hover { background-color: rgba(60, 64, 67, 0.08) !important; } @media (prefers-color-scheme: dark) { button[data-testid="composer-button-prompts"]:hover { background-color: rgba(232, 234, 237, 0.08) !important; } } `); document.head.appendChild(styleElement); } const btn = document.createElement('button'); btn.setAttribute('data-testid', 'composer-button-prompts'); btn.type = 'button'; btn.style.backgroundColor = 'transparent'; btn.style.border = 'none'; btn.style.boxShadow = 'none'; btn.style.borderRadius = '50%'; btn.style.width = '48px'; btn.style.height = '48px'; btn.style.padding = '0'; btn.style.margin = '0'; btn.style.cursor = 'pointer'; btn.style.display = 'inline-flex'; btn.style.alignItems = 'center'; btn.style.justifyContent = 'center'; btn.style.transition = 'background-color 150ms ease-in-out'; setSafeInnerHTML(btn, ``); createCustomTooltip(btn, getTranslation('prompts')); return btn; } // QWEN function createQwenButton() { const btn = document.createElement('button'); btn.className = 'chat-input-feature-btn'; btn.setAttribute('data-testid', 'composer-button-prompts'); setSafeInnerHTML(btn, `${getTranslation('prompt')}`); return btn; } // Z.AI function createZaiButton() { const btnWrapper = document.createElement('div'); setSafeInnerHTML(btnWrapper, ``); const btn = btnWrapper.firstElementChild; btn.setAttribute('data-testid', 'composer-button-prompts'); return btn; } // GEMINI function createGeminiButton() { const btn = document.createElement('button'); btn.setAttribute('data-testid', 'composer-button-prompts'); btn.className = 'mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-primary mat-mdc-tooltip-trigger'; const svgHTML = ``; setSafeInnerHTML(btn, svgHTML); createCustomTooltip(btn, getTranslation('prompt'), 'bottom'); return btn; } // LMARENA function createLmarenaButton() { const btn = document.createElement('button'); btn.setAttribute('data-testid', 'composer-button-prompts'); btn.className = 'inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ring-offset-2 focus-visible:ring-offset-surface-primary disabled:pointer-events-none disabled:opacity-50 text-interactive-active border border-border-faint bg-transparent hover:text-interactive-normal active:text-text-tertiary h-8 w-8 p-2 rounded-md active:scale-[0.96] active:transition-transform active:duration-75 transition-colors duration-150 ease-out hover:shadow-sm hover:bg-interactive-normal/10 hover:border-interactive-normal/10'; btn.type = 'button'; setSafeInnerHTML(btn, ``); createCustomTooltip(btn, getTranslation('prompt')); return btn; } // KIMI function createKimiButton() { const referenceButton = document.querySelector('.deep-research-switch.normal'); if (!referenceButton) { return null; } const btn = referenceButton.cloneNode(true); btn.setAttribute('data-testid', 'composer-button-prompts'); const oldSvg = btn.querySelector('svg'); const textSpan = btn.querySelector('span'); const newSvgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); if (oldSvg) newSvgElement.setAttribute('class', oldSvg.getAttribute('class')); newSvgElement.setAttribute('width', '1em'); newSvgElement.setAttribute('height', '1em'); newSvgElement.setAttribute('viewBox', '0 0 20 20'); newSvgElement.setAttribute('fill', 'currentColor'); setSafeInnerHTML(newSvgElement, ``); if (oldSvg) { oldSvg.replaceWith(newSvgElement); } if (textSpan) { textSpan.textContent = getTranslation('prompt'); } return btn; } // CLAUDE function createClaudeButton() { const wrapper = document.createElement('div'); wrapper.className = 'relative shrink-0'; const btnHTML = ``; setSafeInnerHTML(wrapper, btnHTML); const btn = wrapper.querySelector('button'); createCustomTooltip(btn, getTranslation('prompts')); return wrapper; } // #endregion // #region CRIAR MENU E MODAIS function createPromptMenu() { const menu = document.createElement('div'); menu.className = 'prompt-menu'; menu.id = 'prompt-menu-container'; return menu; } function createPromptModal() { const overlay = document.createElement('div'); overlay.className = 'mp-overlay mp-hidden'; overlay.id = '__ap_modal_overlay'; const box = document.createElement('div'); box.className = 'mp-modal-box'; box.onclick = e => e.stopPropagation(); const modalContentHTML = `
`; setSafeInnerHTML(box, modalContentHTML); overlay.appendChild(box); return overlay; } function createLanguageModal() { const overlay = document.createElement('div'); overlay.className = 'mp-overlay mp-hidden lang-overlay'; overlay.id = '__ap_lang_modal_overlay'; overlay.onclick = () => hideModal(overlay); const box = document.createElement('div'); box.className = 'mp-modal-box lang-box'; box.onclick = (e) => e.stopPropagation(); const buttonsContainer = document.createElement('div'); buttonsContainer.className = 'lang-buttons-container'; Object.keys(translations).forEach((langKey, index) => { const btn = document.createElement('button'); btn.className = 'lang-button'; btn.textContent = translations[langKey].langName; btn.style.animationDelay = `${index * 60}ms`; btn.onclick = async () => { await GM_setValue(LANG_STORAGE_KEY, langKey); window.location.reload(); }; buttonsContainer.appendChild(btn); }); box.appendChild(buttonsContainer); overlay.appendChild(box); return overlay; } function showModal(modal) { if (!modal) return; modal.classList.remove('mp-hidden'); setTimeout(() => modal.classList.add('visible'), 10); } function hideModal(modal) { if (!modal) return; modal.classList.remove('visible'); setTimeout(() => modal.classList.add('mp-hidden'), 200); } function openPromptModal(item = null, index = -1) { if (!currentModal) return; const isEditing = !!item; currentModal.dataset.index = index; currentModal.querySelector('.modal-title').textContent = isEditing ? getTranslation('editPrompt') : getTranslation('newPrompt'); document.getElementById('__ap_title').value = item?.title || ''; document.getElementById('__ap_text').value = item?.text || ''; document.getElementById('__ap_use_placeholders').checked = item?.usePlaceholders || false; showModal(currentModal); setTimeout(() => document.getElementById('__ap_title').focus(), 100); } function createPlaceholderModal() { const overlay = document.createElement('div'); overlay.className = 'mp-overlay mp-hidden'; overlay.id = '__ap_placeholder_modal_overlay'; const box = document.createElement('div'); box.className = 'mp-modal-box'; box.onclick = e => e.stopPropagation(); const modalContentHTML = `
`; setSafeInnerHTML(box, modalContentHTML); overlay.appendChild(box); return overlay; } // #endregion // #region FUNÇÕES AUXILIARES function moveCursorToEnd(editor) { setTimeout(() => { try { editor.focus(); if (currentPlatform === 'gemini') { const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(editor); range.collapse(false); selection.removeAllRanges(); selection.addRange(range); editor.scrollTop = editor.scrollHeight; } else if (currentPlatform === 'chatgpt') { const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(editor); range.collapse(false); selection.removeAllRanges(); selection.addRange(range); const scrollContainer = editor.parentElement; if (scrollContainer) { scrollContainer.scrollTop = scrollContainer.scrollHeight; } } else if (currentPlatform === 'googlestudio') { const textLength = editor.value.length; editor.selectionStart = editor.selectionEnd = textLength; let scrollContainer = editor.parentElement; let i = 0; while (scrollContainer && i < 10) { const style = window.getComputedStyle(scrollContainer); if (style.overflowY === 'auto' || style.overflowY === 'scroll') { break; } scrollContainer = scrollContainer.parentElement; i++; } if (scrollContainer) { scrollContainer.scrollTop = scrollContainer.scrollHeight; } } else { const textLength = editor.value.length; editor.selectionStart = editor.selectionEnd = textLength; editor.scrollTop = editor.scrollHeight; } } catch (e) { } }, 10); } function closeMenu() { if (currentMenu && currentMenu.classList.contains('visible')) { currentMenu.classList.remove('visible'); } } function positionMenu(menu, button) { const btnRect = button.getBoundingClientRect(); const menuHeight = menu.offsetHeight; const menuWidth = menu.offsetWidth; const viewportHeight = window.innerHeight; const viewportWidth = window.innerWidth; const margin = 8; let top, left; const spaceBelow = viewportHeight - btnRect.bottom - margin; const spaceAbove = btnRect.top - margin; if (spaceBelow >= menuHeight) { top = btnRect.bottom + margin; } else if (spaceAbove >= menuHeight) { top = btnRect.top - menuHeight - margin; } else { top = Math.max(margin, viewportHeight - menuHeight - margin); } const spaceRight = viewportWidth - btnRect.left - margin; const spaceLeft = btnRect.right - margin; if (spaceRight >= menuWidth) { left = btnRect.left; } else if (spaceLeft >= menuWidth) { left = btnRect.right - menuWidth; } else { left = (viewportWidth - menuWidth) / 2; } menu.style.top = `${Math.max(margin, Math.min(top, viewportHeight - menuHeight - margin))}px`; menu.style.left = `${Math.max(margin, Math.min(left, viewportWidth - menuWidth - margin))}px`; } async function refreshMenu() { if (!currentMenu) return; const listContainer = document.createElement('div'); listContainer.className = 'prompt-menu-list'; const items = await getAll(); if (!items.length) { setSafeInnerHTML(listContainer, `
${getTranslation('noSavedPrompts')}
`); } else { items.forEach((p, index) => { const row = document.createElement('div'); row.className = 'prompt-item-row'; const titleDiv = document.createElement('div'); titleDiv.className = 'prompt-title'; titleDiv.textContent = p.title; titleDiv.onclick = (e) => { e.stopPropagation(); if (p.usePlaceholders) { const placeholderRegex = /\[([^\]]+)\]/g; const placeholders = [...p.text.matchAll(placeholderRegex)]; if (placeholders.length > 0) { openPlaceholderModal(p, index, placeholders); } else { insertPrompt(p, index); } } else { insertPrompt(p, index); } closeMenu(); }; const actionsDiv = document.createElement('div'); actionsDiv.className = 'prompt-actions'; const btnE = document.createElement('button'); btnE.textContent = getTranslation('edit'); btnE.className = 'action-btn edit'; btnE.onclick = (e) => { e.stopPropagation(); openPromptModal(p, index); }; const btnD = document.createElement('button'); btnD.textContent = getTranslation('delete'); btnD.className = 'action-btn delete'; btnD.onclick = (e) => { e.stopPropagation(); if (confirm(getTranslation('confirmDelete', { title: p.title }))) remove(index).then(refreshMenu); }; actionsDiv.appendChild(btnE); actionsDiv.appendChild(btnD); row.appendChild(titleDiv); row.appendChild(actionsDiv); listContainer.appendChild(row); }); } const addSection = document.createElement('div'); addSection.className = 'menu-section'; const addBtn = document.createElement('div'); addBtn.className = 'menu-button'; setSafeInnerHTML(addBtn, `${getTranslation('addPrompt')}`); addBtn.onclick = (e) => { e.stopPropagation(); openPromptModal(); }; addSection.appendChild(addBtn); const footer = document.createElement('div'); footer.className = 'menu-footer'; const importExportContainer = document.createElement('div'); importExportContainer.className = 'import-export-container'; const exportBtn = document.createElement('div'); exportBtn.className = 'menu-button'; exportBtn.textContent = getTranslation('export'); exportBtn.onclick = (e) => { e.stopPropagation(); exportPrompts(); }; const importBtn = document.createElement('div'); importBtn.className = 'menu-button'; importBtn.textContent = getTranslation('import'); importBtn.onclick = (e) => { e.stopPropagation(); importPrompts(); }; const divider = document.createElement('div'); divider.className = 'divider'; importExportContainer.appendChild(exportBtn); importExportContainer.appendChild(divider); importExportContainer.appendChild(importBtn); footer.appendChild(importExportContainer); setSafeInnerHTML(currentMenu, ''); currentMenu.appendChild(listContainer); currentMenu.appendChild(addSection); currentMenu.appendChild(footer); } function openPlaceholderModal(item, index, placeholders) { if (!currentPlaceholderModal) return; const container = document.getElementById('__ap_placeholders_container'); setSafeInnerHTML(container, ''); currentPlaceholderModal.dataset.prompt = JSON.stringify(item); currentPlaceholderModal.dataset.index = index; const uniquePlaceholders = [...new Map(placeholders.map(p => [p[1], p])).values()]; uniquePlaceholders.forEach((match, i) => { const placeholderText = match[1]; const formGroup = document.createElement('div'); formGroup.className = 'form-group'; const label = document.createElement('label'); label.className = 'form-label'; label.textContent = placeholderText; const textarea = document.createElement('textarea'); textarea.className = 'form-input'; textarea.dataset.placeholder = placeholderText; textarea.rows = 1; textarea.style.resize = 'vertical'; textarea.style.height = 'auto'; textarea.addEventListener('keydown', (event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); document.getElementById('__ap_insert_prompt').click(); } }); formGroup.appendChild(label); formGroup.appendChild(textarea); container.appendChild(formGroup); }); showModal(currentPlaceholderModal); setTimeout(() => container.querySelector('input')?.focus(), 100); } // #endregion // #region DETECTAR E INTERAGIR function detectPlatform() { const hostname = window.location.hostname; if (hostname.includes('chatgpt.com')) return 'chatgpt'; if (hostname.includes('deepseek.com')) return 'deepseek'; if (hostname.includes('aistudio.google.com')) return 'googlestudio'; if (hostname.includes('chat.qwen.ai')) return 'qwen'; if (hostname.includes('chat.z.ai')) return 'zai'; if (hostname.includes('gemini.google.com')) return 'gemini'; if (hostname.includes('lmarena.ai')) return 'lmarena'; if (hostname.includes('kimi.com')) return 'kimi'; if (hostname.includes('claude.ai')) return 'claude'; return null; } async function insertPrompt(promptItem, index) { const platformSelectors = { chatgpt: '#prompt-textarea', deepseek: 'textarea[placeholder="Message DeepSeek"]', googlestudio: 'ms-autosize-textarea textarea', qwen: 'textarea#chat-input', zai: 'textarea#chat-input', gemini: 'div.ql-editor[contenteditable="true"]', lmarena: 'textarea[name="message"]', kimi: 'div.chat-input-editor[contenteditable="true"]', claude: 'div.ProseMirror[contenteditable="true"]' }; const editor = document.querySelector(platformSelectors[currentPlatform]); if (!editor) { alert(getTranslation('editorNotFound', { platform: currentPlatform })); return; } editor.focus(); setTimeout(() => { const isFirefox = navigator.userAgent.toLowerCase().includes('firefox'); if (isFirefox && currentPlatform === 'kimi') { document.execCommand('selectAll', false, null); document.execCommand('insertText', false, promptItem.text); } else if (isFirefox && (currentPlatform === 'chatgpt' || currentPlatform === 'claude')) { setSafeInnerHTML(editor, ''); const lines = promptItem.text.split('\n'); lines.forEach(line => { const p = document.createElement('p'); if (line.trim() === '') { p.appendChild(document.createElement('br')); } else { p.textContent = line; } editor.appendChild(p); }); editor.dispatchEvent(new Event('input', { bubbles: true, composed: true })); } else if (currentPlatform === 'gemini') { let p = editor.querySelector('p') || document.createElement('p'); setSafeInnerHTML(editor, ''); p.textContent = promptItem.text; editor.appendChild(p); editor.dispatchEvent(new Event('input', { bubbles: true, composed: true })); } else { const dt = new DataTransfer(); dt.setData('text/plain', promptItem.text); editor.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); if (editor.value !== undefined && editor.value !== promptItem.text) { const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; setter.call(editor, promptItem.text); editor.dispatchEvent(new Event('input', { bubbles: true })); } } moveCursorToEnd(editor); }, 100); let prompts = await getAll(); if (index > 0) { const item = prompts.splice(index, 1)[0]; prompts.unshift(item); await GM_setValue(PROMPT_STORAGE_KEY, prompts); } } async function exportPrompts() { const prompts = await getAll(); if(prompts.length===0){alert(getTranslation('noPromptsToExport'));return} const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([JSON.stringify(prompts,null,2)],{type:'application/json'})); a.download=getTranslation('fileName'); a.click(); URL.revokeObjectURL(a.href); closeMenu(); } function importPrompts() { const input=document.createElement('input'); input.type='file'; input.accept='.json'; input.onchange=e=>{ const file=e.target.files[0]; if(!file)return; const reader=new FileReader(); reader.onload=async event=>{ try{ const imported=JSON.parse(event.target.result); if(!Array.isArray(imported))throw new Error("Not an array."); const current=await getAll(); const newPrompts = imported.map(p => ({ title: p.title || 'No Title', text: p.text || '', usePlaceholders: p.usePlaceholders || false })); await GM_setValue(PROMPT_STORAGE_KEY,[...current,...newPrompts]); await refreshMenu(); alert(getTranslation('promptsImported',{count:newPrompts.length})); }catch(err){alert(getTranslation('errorImporting',{error:err.message}))} }; reader.readAsText(file); }; input.click(); closeMenu(); } function cleanup() { if (currentButton) { currentButton.remove (); currentButton = null; } if (currentMenu) { currentMenu.remove (); currentMenu = null; } if (currentModal) { currentModal.remove (); currentModal = null; } if (languageModal) { languageModal.remove (); languageModal = null; } if (currentPlaceholderModal) { currentPlaceholderModal.remove(); currentPlaceholderModal = null; } isInitialized = false; } // #endregion // #region INICIALIZAÇÃO E OBSERVAÇÃO async function initUI() { if (pageObserver) pageObserver.disconnect(); cleanup(); currentPlatform = detectPlatform(); if (!currentPlatform) return; try { let btn, elementToInsert, insertionPoint, insertionMethod = 'before'; // CHATGPT if (currentPlatform === 'chatgpt') { insertionPoint = await waitFor('div[class*="[grid-area:leading]"]'); insertionPoint.style.display = 'flex'; insertionPoint.style.alignItems = 'center'; btn = createChatGPTButton(); elementToInsert = btn; insertionMethod = 'append'; // DEEPSEEK } else if (currentPlatform === 'deepseek') { const container = await waitFor('.ec4f5d61'); insertionPoint = container.querySelector('.bf38813a'); const allButtons = Array.from(container.querySelectorAll('button')); const refBtn = allButtons.find(b => b.textContent.trim() === 'Search'); if (!refBtn || !insertionPoint) throw new Error('DeepSeek UI reference button "Search" not found.'); btn = createDeepSeekButton(); btn.className = refBtn.className; elementToInsert = btn; // GOOGLE STUDIO } else if (currentPlatform === 'googlestudio') { insertionPoint = await waitFor('ms-add-chunk-menu', 5000).then(el => el.closest('.button-wrapper')); const wrapper = document.createElement('div'); wrapper.className = 'button-wrapper'; btn = createGoogleStudioButton(); wrapper.appendChild(btn); elementToInsert = wrapper; const parent = insertionPoint.closest('.prompt-input-wrapper-container'); if (parent) parent.style.alignItems = 'center'; // QWEN } else if (currentPlatform === 'qwen') { const referenceButton = await waitFor('button.websearch_button', 5000); const buttonContainer = referenceButton.parentElement; if (!buttonContainer) throw new Error('Qwen button container not found.'); insertionPoint = buttonContainer.firstChild; insertionMethod = 'before'; btn = createQwenButton(); elementToInsert = btn; const qwenPositionObserver = new MutationObserver(() => { const myButton = buttonContainer.querySelector('button[data-testid="composer-button-prompts"]'); if (myButton && buttonContainer.firstElementChild !== myButton) { buttonContainer.prepend(myButton); } }); qwenPositionObserver.observe(buttonContainer, { childList: true }); // Z.AI } else if (currentPlatform === 'zai') { const referenceElement = await waitFor('button[data-autothink="true"]', 8000); insertionPoint = referenceElement.closest('.flex.gap-\\[8px\\].items-center'); if (!insertionPoint) throw new Error('Z.ai button container not found.'); btn = createZaiButton(); elementToInsert = btn; insertionMethod = 'append'; // GEMENI } else if (currentPlatform === 'gemini') { insertionPoint = await waitFor('uploader', 8000); btn = createGeminiButton(); elementToInsert = btn; insertionMethod = 'after'; const wrapper = insertionPoint.parentElement; if (wrapper) { wrapper.style.display = 'flex'; wrapper.style.alignItems = 'center'; wrapper.style.gap = '3px'; } // LM ARENA } else if (currentPlatform === 'lmarena') { insertionPoint = await waitFor('div[data-sentry-component="SelectChatModality"]', 8000); btn = createLmarenaButton(); elementToInsert = btn; insertionMethod = 'append'; // KIMI } else if (currentPlatform === 'kimi') { insertionPoint = await waitFor('div.left-area', 8000); btn = createKimiButton(); if (!btn) return; elementToInsert = btn; const ensureButtonIsPresent = () => { if (!insertionPoint.contains(elementToInsert)) { insertionPoint.appendChild(elementToInsert); } }; ensureButtonIsPresent(); const guardianObserver = new MutationObserver(ensureButtonIsPresent); guardianObserver.observe(insertionPoint, { childList: true }); insertionMethod = 'handled_by_kimi_guardian'; // CLAUDE } else if (currentPlatform === 'claude') { insertionPoint = await waitFor('button[data-testid="input-menu-plus"]', 8000).then(el => el.closest('div.relative.shrink-0')); btn = createClaudeButton(); elementToInsert = btn; insertionMethod = 'after'; } if (!btn || !insertionPoint) return; currentButton = elementToInsert; const clickable = btn; if (insertionMethod === 'append') { insertionPoint.appendChild(elementToInsert); } else if (insertionMethod === 'before') { insertionPoint.parentNode.insertBefore(elementToInsert, insertionPoint); } else if (insertionMethod === 'after') { insertionPoint.parentNode.insertBefore(elementToInsert, insertionPoint.nextSibling); } currentMenu = createPromptMenu(); currentModal = createPromptModal(); languageModal = createLanguageModal(); currentPlaceholderModal = createPlaceholderModal(); document.body.appendChild(currentMenu); document.body.appendChild(currentModal); document.body.appendChild(languageModal); document.body.appendChild(currentPlaceholderModal); clickable.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); const menu = currentMenu; if (menu.classList.contains('visible')) { closeMenu(); return; } refreshMenu().then(() => { positionMenu(menu, clickable); menu.classList.add('visible'); }); }); currentModal.querySelector('#__ap_save').onclick = async (e) => { e.stopPropagation(); const index = parseInt(currentModal.dataset.index, 10); const title = document.getElementById('__ap_title').value.trim(); const text = document.getElementById('__ap_text').value.trim(); const usePlaceholders = document.getElementById('__ap_use_placeholders').checked; if (!title || !text) { alert(getTranslation('requiredFields')); return; } const op = index > -1 ? update(index, { title, text, usePlaceholders }) : addItem({ title, text, usePlaceholders }); op.then(() => { hideModal(currentModal); refreshMenu(); }); }; currentModal.querySelector('#__ap_close_prompt').onclick = (e) => { e.stopPropagation(); hideModal(currentModal); }; currentPlaceholderModal.querySelector('#__ap_insert_prompt').onclick = async (e) => { e.stopPropagation(); const item = JSON.parse(currentPlaceholderModal.dataset.prompt); const index = parseInt(currentPlaceholderModal.dataset.index, 10); let completedText = item.text; const textareas = currentPlaceholderModal.querySelectorAll('#__ap_placeholders_container textarea'); textareas.forEach(textarea => { const placeholder = textarea.dataset.placeholder; const userValue = textarea.value; const regex = new RegExp(`\\[${placeholder.replace(/[\.\+\*?\[\^\]\$\(\)\{\}\=\!<>\|\:\-]/g, '\\$&')}\\]`, 'g'); completedText = completedText.replace(regex, userValue); }); const finalPrompt = { ...item, text: completedText }; await insertPrompt(finalPrompt, index); hideModal(currentPlaceholderModal); }; currentPlaceholderModal.querySelector('#__ap_close_placeholder').onclick = (e) => { e.stopPropagation(); hideModal(currentPlaceholderModal); }; isInitialized = true; } catch (error) { cleanup(); } finally { setupPageObserver(); } } const debouncedTryInit = debounce(tryInit, 500); function setupPageObserver() { if (pageObserver) pageObserver.disconnect(); pageObserver = new MutationObserver(() => { if (!document.body.contains(currentButton)) { debouncedTryInit(); } }); pageObserver.observe(document.body, { childList: true, subtree: true }); } function setupGlobalEventListeners() { document.addEventListener('click', ev => { if (!currentMenu || !currentButton) return; if (ev.target.closest('#prompt-menu-container, [data-testid="composer-button-prompts"]')) return; closeMenu(); }); document.addEventListener('keydown', ev => { if (ev.key === 'Escape') { closeMenu(); if (currentModal && currentModal.classList.contains('visible')) hideModal(currentModal); if (languageModal && languageModal.classList.contains('visible')) hideModal(languageModal); if (currentPlaceholderModal && currentPlaceholderModal.classList.contains('visible')) hideModal(currentPlaceholderModal); } }); window.addEventListener('resize', debounce(() => { if (currentMenu && currentMenu.classList.contains('visible')) { positionMenu(currentMenu, currentButton); } }, 100)); } function tryInit() { if (isInitializing) return; if (isInitialized && currentButton && document.body.contains(currentButton) && currentPlatform === detectPlatform()) { return; } isInitializing = true; initUI().finally(() => { isInitializing = false; }); } // #endregion // #region INICIAR SCRIPT async function start() { await determineLanguage(); injectGlobalStyles(); setupGlobalEventListeners(); GM_registerMenuCommand(getTranslation('languageSettings'), () => { if (!languageModal) { languageModal = createLanguageModal(); document.body.appendChild(languageModal); } showModal(languageModal); }); tryInit(); } start(); // #endregion })();