- // ==UserScript==
- // @name GPT Prompt Manager: Deepseek and ChatGPT
- // @namespace http://tampermonkey.net/
- // @version 1.0.4
- // @author Minhaz Mahmood
- // @description Manage, store, and quickly insert GPT prompts with categorization, templating, tagging, rating, and usage tracking.
- // @match *://chatgpt.com/*
- // @match *://deepseek.com/*
- // @match *://chat.deepseek.com/*
- // @match *://chat.openai.com/*
- // @match *://*.chat.openai.com/*
- // @grant GM_setValue
- // @grant GM_getValue
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- /***********************************************************************
- * Configuration
- ***********************************************************************/
- const CONFIG = {
- MAX_ITEMS: 200,
- MAX_FAVORITES: 10,
- TOAST_DURATION: 2000,
- CONFIRM_TIMEOUT: 5000,
- STORAGE_KEY: 'gpt-prompts',
- KEYBOARD_SHORTCUT: { ctrlKey: true, altKey: true, key: 'p' },
- TABS: ['Coding', 'Writing', 'Research', 'General', 'Templates', 'Archive'],
- // Selector for ChatGPT input field; update if ChatGPT's UI changes.
- CHATGPT_INPUT_SELECTOR: 'textarea[data-id="root"]'
- };
-
- /***********************************************************************
- * CSS Styles and UI Layout
- ***********************************************************************/
- const styles = `
- /* General transitions */
- .prompt-manager, .clip-toggle, .prompt-content, .prompt-bottom-actions,
- .prompt-header, .prompt-title, .prompt-close, .prompt-toast, .prompt-card,
- .prompt-preview, .prompt-actions, .prompt-btn, .prompt-search, .prompt-controls,
- .prompt-edit-icon, .prompt-save-icon, .prompt-drag-handle, .prompt-theme-toggle,
- .prompt-tabs, .prompt-tab, .prompt-tags, .prompt-import, .prompt-export {
- transition: all 0.2s ease;
- }
-
- /* Manager container */
- .prompt-manager {
- position: fixed;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- width: 640px;
- height: 720px;
- background: #1a1b1e;
- border-radius: 16px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
- z-index: 99999;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- display: flex;
- flex-direction: column;
- opacity: 0;
- visibility: hidden;
- user-select: none;
- resize: both;
- overflow: hidden;
- }
- .prompt-manager.open {
- opacity: 1;
- visibility: visible;
- }
-
- /* Floating Toggle Button (exact as original) */
- .clip-toggle {
- position: fixed;
- right: 340px;
- top: 10px;
- background: #2c2d31;
- border: none;
- border-radius: 12px;
- padding: 12px;
- cursor: pointer;
- z-index: 10001;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .clip-toggle.hidden {
- opacity: 0;
- visibility: hidden;
- pointer-events: none;
- }
- .clip-toggle:hover {
- transform: scale(1.05);
- background: #3a3b3f;
- }
-
- /* Header and Tabs */
- .prompt-header {
- padding: 10px 20px;
- color: #fff;
- border-bottom: 1px solid #2c2d31;
- display: flex;
- justify-content: space-between;
- align-items: center;
- cursor: move;
- }
- .prompt-title {
- margin: 0;
- font-size: 18px;
- font-weight: 600;
- }
- .prompt-close {
- background: transparent;
- border: none;
- color: #6b7280;
- font-size: 24px;
- cursor: pointer;
- padding: 4px 8px;
- border-radius: 6px;
- }
- .prompt-close:hover {
- background: rgba(255, 255, 255, 0.1);
- color: #fff;
- }
- .prompt-tabs {
- display: flex;
- gap: 12px;
- padding: 0 20px;
- border-bottom: 1px solid #2c2d31;
- background: #24252a;
- }
- .prompt-tab {
- cursor: pointer;
- padding: 8px 12px;
- color: #ccc;
- border-bottom: 2px solid transparent;
- }
- .prompt-tab.active {
- color: #fff;
- border-color: #98c379;
- }
-
- /* Controls: Search, Theme Toggle, Import/Export */
- .prompt-controls {
- margin: 10px 20px;
- display: flex;
- gap: 16px;
- align-items: center;
- flex-wrap: wrap;
- }
- .prompt-search {
- background: #2c2d31;
- color: #fff;
- border: 1px solid #3a3b3f;
- padding: 8px 12px;
- border-radius: 6px;
- flex: 1;
- }
- .prompt-theme-toggle, .prompt-import, .prompt-export {
- background: #5a67d8;
- color: #fff;
- padding: 8px 12px;
- border: none;
- border-radius: 6px;
- cursor: pointer;
- }
- .prompt-theme-toggle:hover, .prompt-import:hover, .prompt-export:hover {
- background: #6875f5;
- }
-
- /* Content Area */
- .prompt-content {
- flex: 1;
- overflow-y: auto;
- padding: 10px 20px;
- margin-bottom: 60px;
- }
- .prompt-content::-webkit-scrollbar {
- width: 6px;
- }
- .prompt-content::-webkit-scrollbar-track {
- background: #1a1b1e;
- }
- .prompt-content::-webkit-scrollbar-thumb {
- background: #2c2d31;
- border-radius: 3px;
- }
- .prompt-content::-webkit-scrollbar-thumb:hover {
- background: #3a3b3f;
- }
-
- /* Bottom Actions */
- .prompt-bottom-actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px 20px;
- background: #2c2d31;
- border-radius: 0 0 16px 16px;
- }
- .prompt-save, .prompt-save-clipboard, .prompt-clear-all {
- height: 40px;
- padding: 0 24px;
- border-radius: 8px;
- cursor: pointer;
- font-weight: 600;
- font-size: 14px;
- line-height: 40px;
- white-space: nowrap;
- border: none;
- }
- .prompt-save {
- background: #98c379;
- color: #1a1b1e;
- box-shadow: 0 2px 8px rgba(152, 195, 121, 0.2);
- }
- .prompt-save:hover {
- background: #a9d389;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(152, 195, 121, 0.3);
- }
- .prompt-save:disabled {
- background: #4a4b4f;
- cursor: not-allowed;
- }
- .prompt-save-clipboard {
- background: #5a67d8;
- color: #fff;
- box-shadow: 0 2px 8px rgba(90, 103, 216, 0.2);
- }
- .prompt-save-clipboard:hover {
- background: #6875f5;
- }
- .prompt-clear-all {
- background: #dc2626;
- color: #fff;
- box-shadow: 0 2px 8px rgba(220, 38, 38, 0.2);
- padding-right: 20px;
- text-align: center;
- }
- .prompt-clear-all:hover {
- background: #ef4444;
- }
- .prompt-clear-all.confirm {
- background: #991b1b;
- }
-
- /* Prompt Card Styles */
- .prompt-card {
- background: #2c2d31;
- border-radius: 12px;
- padding: 12px;
- margin-bottom: 12px;
- color: #fff;
- border: 1px solid #3a3b3f;
- }
- .prompt-card:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
- border-color: #4a4b4f;
- }
- .prompt-title-wrapper {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 4px;
- }
- .prompt-title-display {
- font-size: 16px;
- color: #fff;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex: 1;
- }
- .prompt-edit-icon, .prompt-save-icon {
- background: transparent;
- border: none;
- color: #6b7280;
- font-size: 16px;
- cursor: pointer;
- padding: 4px 8px;
- border-radius: 6px;
- }
- .prompt-edit-icon:hover, .prompt-save-icon:hover {
- background: rgba(255, 255, 255, 0.1);
- color: #fff;
- }
- .prompt-preview {
- font-size: 14px;
- color: #d1d5db;
- margin: 4px 0 8px 0;
- line-height: 1.5;
- max-height: 90px;
- overflow-y: auto;
- white-space: pre-wrap;
- word-break: break-word;
- }
- .prompt-info {
- font-size: 12px;
- color: #9ca3af;
- margin-bottom: 4px;
- }
- .prompt-tags {
- margin-top: 4px;
- font-size: 12px;
- }
- .prompt-tags span {
- color: #98c379;
- cursor: pointer;
- margin-right: 6px;
- }
-
- /* Action buttons within each prompt card */
- .prompt-actions {
- display: flex;
- gap: 8px;
- justify-content: flex-end;
- border-top: 1px solid #3a3b3f;
- padding-top: 8px;
- margin-top: 4px;
- }
- .prompt-btn {
- height: 32px;
- padding: 0 12px;
- background: transparent;
- border: 1px solid #4a4b4f;
- color: #fff;
- border-radius: 6px;
- font-size: 13px;
- line-height: 30px;
- }
- .prompt-btn:hover {
- background: #3a3b3f;
- border-color: #5a5b5f;
- }
- .prompt-btn.delete {
- color: #ef4444;
- border-color: #ef4444;
- }
- .prompt-btn.delete:hover {
- background: rgba(239, 68, 68, 0.1);
- }
-
- /* Rating stars */
- .prompt-rating {
- display: flex;
- gap: 4px;
- align-items: center;
- margin-right: auto;
- }
- .prompt-rating-star {
- cursor: pointer;
- color: #ccc;
- }
- .prompt-rating-star.filled {
- color: #ffc107;
- }
-
- /* Resize drag handle */
- .prompt-drag-handle {
- position: absolute;
- right: 2px;
- bottom: 2px;
- width: 16px;
- height: 16px;
- cursor: nwse-resize;
- background: rgba(255, 255, 255, 0.3);
- border-radius: 3px;
- }
-
- /* Toast notification styling */
- .prompt-toast {
- position: fixed;
- bottom: 20px;
- left: 50%;
- transform: translateX(-50%);
- background: #333;
- color: #fff;
- padding: 10px 16px;
- border-radius: 8px;
- z-index: 999999;
- }
-
- /* Light theme overrides */
- .prompt-manager[data-theme="light"] {
- background: #ffffff;
- color: #1a1b1e;
- }
- .prompt-manager[data-theme="light"] .prompt-search {
- background: #f0f0f0;
- color: #1a1b1e;
- }
- .prompt-manager[data-theme="light"] .prompt-card {
- background: #f8f8f8;
- color: #1a1b1e;
- }
- `;
-
- // Append the stylesheet to the document head.
- const styleSheet = document.createElement('style');
- styleSheet.textContent = styles;
- document.head.appendChild(styleSheet);
-
- /***********************************************************************
- * UI Elements: Manager Window & Floating Toggle Button
- ***********************************************************************/
- const manager = document.createElement('div');
- manager.className = 'prompt-manager';
- manager.dataset.theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
-
- // Manager HTML layout with header, tabs, controls, content, and bottom actions.
- manager.innerHTML = `
- <div class="prompt-header">
- <h2 class="prompt-title">GPT Prompt Manager</h2>
- <button class="prompt-close">×</button>
- </div>
- <div class="prompt-tabs">
- ${CONFIG.TABS.map(tab => `<div class="prompt-tab" data-tab="${tab}">${tab}</div>`).join('')}
- </div>
- <div class="prompt-controls">
- <input type="text" class="prompt-search" placeholder="Search prompts by title, content, or tag...">
- <button class="prompt-theme-toggle">Toggle Theme</button>
- <button class="prompt-import">Import</button>
- <button class="prompt-export">Export</button>
- </div>
- <div class="prompt-content"></div>
- <div class="prompt-bottom-actions">
- <button class="prompt-save-clipboard">Save Clipboard</button>
- <button class="prompt-save" disabled>Save Selection</button>
- <button class="prompt-clear-all">Clear All</button>
- </div>
- <div class="prompt-drag-handle"></div>
- `;
-
- // Floating toggle button using the exact SVG and styling from the original script.
- const toggleBtn = document.createElement('button');
- toggleBtn.className = 'clip-toggle';
- toggleBtn.innerHTML = `
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
- <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
- <rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
- </svg>
- `;
-
- /***********************************************************************
- * GPT Prompt Manager Class Definition
- ***********************************************************************/
- class GptPromptManager {
- constructor() {
- this.prompts = this.loadPrompts();
- this.archivedPrompts = this.loadArchivedPrompts();
- this.isOpen = false;
- this.clearAllTimeout = null;
- this.searchTerm = '';
- this.activeTab = 'General';
- this.manualTheme = null; // null indicates following system preference
- this.activeTagFilter = '';
-
- // Append UI elements
- document.body.appendChild(manager);
- document.body.appendChild(toggleBtn);
-
- // Initialize events, theme detection, drag/resize, and initial render.
- this.initEvents();
- this.setupThemeDetection();
- this.makeDraggable();
- this.renderTabs();
- this.renderPrompts();
- this.updateSaveButton();
- }
-
- /***********************************************************************
- * Data Persistence: Loading & Saving Prompts
- ***********************************************************************/
- loadPrompts() {
- try {
- const saved = typeof GM_getValue !== 'undefined'
- ? GM_getValue(CONFIG.STORAGE_KEY)
- : localStorage.getItem(CONFIG.STORAGE_KEY);
- return saved ? JSON.parse(saved) : [];
- } catch (err) {
- console.error('Error loading prompts:', err);
- return [];
- }
- }
-
- loadArchivedPrompts() {
- try {
- const archived = typeof GM_getValue !== 'undefined'
- ? GM_getValue(CONFIG.STORAGE_KEY + '_archived')
- : localStorage.getItem(CONFIG.STORAGE_KEY + '_archived');
- return archived ? JSON.parse(archived) : [];
- } catch (err) {
- console.error('Error loading archived prompts:', err);
- return [];
- }
- }
-
- savePrompts() {
- try {
- const data = JSON.stringify(this.prompts);
- if (typeof GM_setValue !== 'undefined') {
- GM_setValue(CONFIG.STORAGE_KEY, data);
- } else {
- localStorage.setItem(CONFIG.STORAGE_KEY, data);
- }
- } catch (err) {
- console.error('Error saving prompts:', err);
- this.showToast('Error saving prompts');
- }
- }
-
- saveArchivedPrompts() {
- try {
- const data = JSON.stringify(this.archivedPrompts);
- if (typeof GM_setValue !== 'undefined') {
- GM_setValue(CONFIG.STORAGE_KEY + '_archived', data);
- } else {
- localStorage.setItem(CONFIG.STORAGE_KEY + '_archived', data);
- }
- } catch (err) {
- console.error('Error saving archived prompts:', err);
- }
- }
-
- /***********************************************************************
- * Theme Detection and Toggling
- ***********************************************************************/
- setupThemeDetection() {
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- mediaQuery.addEventListener('change', e => {
- if (this.manualTheme === null) {
- manager.dataset.theme = e.matches ? 'dark' : 'light';
- }
- });
- }
-
- /***********************************************************************
- * Event Binding
- ***********************************************************************/
- initEvents() {
- // Toggle manager visibility
- toggleBtn.addEventListener('click', () => this.toggle());
- manager.querySelector('.prompt-close').addEventListener('click', () => this.close());
-
- // Save selected text as a new prompt
- document.addEventListener('selectionchange', () => this.updateSaveButton());
- manager.querySelector('.prompt-save').addEventListener('click', () => {
- const selection = window.getSelection().toString().trim();
- if (selection) {
- this.addPrompt(selection);
- }
- });
-
- // Save clipboard text as a new prompt
- manager.querySelector('.prompt-save-clipboard').addEventListener('click', async () => {
- try {
- const clipboardText = await navigator.clipboard.readText();
- if (clipboardText.trim()) {
- this.addPrompt(clipboardText);
- } else {
- this.showToast('Clipboard is empty');
- }
- } catch (err) {
- console.error('Clipboard error:', err);
- this.showToast('Failed to read clipboard');
- }
- });
-
- // Clear all prompts (with confirmation)
- manager.querySelector('.prompt-clear-all').addEventListener('click', e => {
- this.handleClearAll(e.target);
- });
-
- // Tab switching
- manager.querySelectorAll('.prompt-tab').forEach(tab => {
- tab.addEventListener('click', () => {
- this.activeTab = tab.dataset.tab;
- this.activeTagFilter = '';
- this.renderTabs();
- this.renderPrompts();
- });
- });
-
- // Search functionality
- manager.querySelector('.prompt-search').addEventListener('input', e => {
- this.searchTerm = e.target.value.toLowerCase();
- this.activeTagFilter = '';
- this.renderPrompts();
- });
-
- // Theme toggle
- manager.querySelector('.prompt-theme-toggle').addEventListener('click', () => {
- if (this.manualTheme === 'dark') {
- this.manualTheme = 'light';
- } else if (this.manualTheme === 'light') {
- this.manualTheme = 'dark';
- } else {
- this.manualTheme = (manager.dataset.theme === 'dark') ? 'light' : 'dark';
- }
- manager.dataset.theme = this.manualTheme;
- });
-
- // Import and Export prompts
- manager.querySelector('.prompt-import').addEventListener('click', () => this.importPrompts());
- manager.querySelector('.prompt-export').addEventListener('click', () => this.exportPrompts());
-
- // Global keyboard shortcut: Ctrl+Alt+P
- document.addEventListener('keydown', e => {
- if (e.ctrlKey === CONFIG.KEYBOARD_SHORTCUT.ctrlKey &&
- e.altKey === CONFIG.KEYBOARD_SHORTCUT.altKey &&
- e.key.toLowerCase() === CONFIG.KEYBOARD_SHORTCUT.key) {
- e.preventDefault();
- this.toggle();
- const selection = window.getSelection().toString().trim();
- if (selection) {
- this.addPrompt(selection);
- }
- }
- if (e.key === 'Escape' && this.isOpen) {
- this.close();
- }
- });
-
- // Delegate click events for prompt card actions
- manager.querySelector('.prompt-content').addEventListener('click', e => {
- const card = e.target.closest('.prompt-card');
- if (!card) return;
- const id = parseInt(card.dataset.id);
-
- if (e.target.classList.contains('prompt-edit-icon') ||
- e.target.classList.contains('prompt-save-icon')) {
- this.toggleEditTitle(id, card);
- } else if (e.target.classList.contains('delete')) {
- if (this.activeTab === 'Archive') {
- this.deleteArchivedPrompt(id);
- } else {
- this.removePrompt(id);
- }
- } else if (e.target.classList.contains('favorite')) {
- this.toggleFavorite(id);
- } else if (e.target.classList.contains('move-up')) {
- e.stopPropagation();
- this.movePrompt(id, -1);
- } else if (e.target.classList.contains('move-down')) {
- e.stopPropagation();
- this.movePrompt(id, 1);
- } else if (e.target.classList.contains('copy')) {
- const text = card.querySelector('.prompt-preview').textContent;
- this.copyText(text);
- } else if (e.target.classList.contains('insert')) {
- const text = card.querySelector('.prompt-preview').textContent;
- this.insertIntoChatGpt(text);
- this.incrementUsage(id);
- } else if (e.target.classList.contains('prompt-rating-star')) {
- const starValue = parseInt(e.target.dataset.value);
- this.setRating(id, starValue);
- } else if (e.target.tagName === 'SPAN' && e.target.parentElement.classList.contains('prompt-tags')) {
- // Filter by tag when clicked
- this.activeTagFilter = e.target.textContent.slice(1).toLowerCase();
- manager.querySelector('.prompt-search').value = '';
- this.searchTerm = '';
- this.renderPrompts();
- }
- });
- }
-
- /***********************************************************************
- * Draggable & Resizable Manager
- ***********************************************************************/
- makeDraggable() {
- const header = manager.querySelector('.prompt-header');
- header.addEventListener('mousedown', e => {
- e.preventDefault();
- const offsetX = e.clientX - manager.offsetLeft;
- const offsetY = e.clientY - manager.offsetTop;
- const onMouseMove = ev => {
- manager.style.left = (ev.clientX - offsetX) + 'px';
- manager.style.top = (ev.clientY - offsetY) + 'px';
- };
- const onMouseUp = () => document.removeEventListener('mousemove', onMouseMove);
- document.addEventListener('mousemove', onMouseMove);
- document.addEventListener('mouseup', onMouseUp, { once: true });
- });
-
- // Enable resizing using the drag handle
- const dragHandle = manager.querySelector('.prompt-drag-handle');
- let isResizing = false, startX, startY, initialWidth, initialHeight;
- dragHandle.addEventListener('mousedown', e => {
- e.preventDefault();
- isResizing = true;
- startX = e.clientX;
- startY = e.clientY;
- initialWidth = manager.offsetWidth;
- initialHeight = manager.offsetHeight;
-
- const onMouseMove = ev => {
- if (isResizing) {
- const newWidth = initialWidth + (ev.clientX - startX);
- const newHeight = initialHeight + (ev.clientY - startY);
- manager.style.width = newWidth + 'px';
- manager.style.height = newHeight + 'px';
- }
- };
- const onMouseUp = () => {
- isResizing = false;
- document.removeEventListener('mousemove', onMouseMove);
- };
- document.addEventListener('mousemove', onMouseMove);
- document.addEventListener('mouseup', onMouseUp, { once: true });
- });
- }
-
- /***********************************************************************
- * Prompt Operations: Add, Remove, and Modify Prompts
- ***********************************************************************/
- addPrompt(text) {
- const timestamp = Date.now();
- const category = this.activeTab !== 'Archive' ? this.activeTab : 'General';
- const promptItem = {
- id: timestamp,
- title: `Prompt-${timestamp}`,
- text: text.trim(),
- date: new Date().toISOString(),
- url: window.location.href,
- isFavorite: false,
- category,
- tags: [],
- usageCount: 0,
- rating: 0
- };
-
- // Insert at the beginning of non-favorite items.
- const index = this.prompts.findIndex(p => !p.isFavorite);
- this.prompts.splice(index === -1 ? this.prompts.length : index, 0, promptItem);
-
- // Enforce maximum prompt limit and archive older items if needed.
- if (this.prompts.length > CONFIG.MAX_ITEMS) {
- const nonFavorites = this.prompts.filter(p => !p.isFavorite);
- if (nonFavorites.length) {
- const itemToArchive = nonFavorites[nonFavorites.length - 1];
- this.archivedPrompts.unshift(itemToArchive);
- this.prompts = this.prompts.filter(p => p.id !== itemToArchive.id);
- this.saveArchivedPrompts();
- }
- }
-
- this.savePrompts();
- this.renderPrompts();
- this.showToast('Prompt saved');
- }
-
- removePrompt(id) {
- this.prompts = this.prompts.filter(p => p.id !== id);
- // Attempt to restore one archived prompt if available.
- if (this.archivedPrompts.length && this.prompts.length < CONFIG.MAX_ITEMS) {
- const restoreItem = this.archivedPrompts.find(p => !p.isFavorite);
- if (restoreItem) {
- this.prompts.push(restoreItem);
- this.archivedPrompts = this.archivedPrompts.filter(p => p.id !== restoreItem.id);
- this.saveArchivedPrompts();
- this.showToast('Restored a prompt from archive');
- }
- }
- this.savePrompts();
- this.renderPrompts();
- this.showToast('Prompt deleted');
- }
-
- deleteArchivedPrompt(id) {
- this.archivedPrompts = this.archivedPrompts.filter(p => p.id !== id);
- this.saveArchivedPrompts();
- this.renderPrompts();
- this.showToast('Archived prompt permanently deleted');
- }
-
- movePrompt(id, direction) {
- const idx = this.prompts.findIndex(p => p.id === id);
- if (idx === -1) return;
- const newIdx = idx + direction;
- if (newIdx < 0 || newIdx >= this.prompts.length) {
- this.showToast('Cannot move prompt further');
- return;
- }
- const item = this.prompts[idx];
- const target = this.prompts[newIdx];
- // Prevent moving across favorite boundaries.
- if ((item.isFavorite && !target.isFavorite) || (!item.isFavorite && target.isFavorite)) {
- this.showToast('Cannot move across favorite sections');
- return;
- }
- this.prompts.splice(idx, 1);
- this.prompts.splice(newIdx, 0, item);
- this.savePrompts();
- this.renderPrompts();
- }
-
- toggleFavorite(id) {
- const item = this.prompts.find(p => p.id === id);
- if (!item) return;
- const favoriteCount = this.prompts.filter(p => p.isFavorite).length;
- if (!item.isFavorite && favoriteCount >= CONFIG.MAX_FAVORITES) {
- this.showToast(`Maximum ${CONFIG.MAX_FAVORITES} favorites allowed`);
- return;
- }
- const idx = this.prompts.indexOf(item);
- this.prompts.splice(idx, 1);
- item.isFavorite = !item.isFavorite;
- if (item.isFavorite) {
- const lastFav = this.prompts.findLastIndex(p => p.isFavorite);
- this.prompts.splice(lastFav + 1, 0, item);
- } else {
- const firstNonFav = this.prompts.findIndex(p => !p.isFavorite);
- this.prompts.splice(firstNonFav === -1 ? this.prompts.length : firstNonFav, 0, item);
- }
- this.savePrompts();
- this.renderPrompts();
- this.showToast(item.isFavorite ? 'Marked as favorite' : 'Removed favorite');
- }
-
- copyText(text) {
- navigator.clipboard.writeText(text)
- .then(() => this.showToast('Copied to clipboard'))
- .catch(err => {
- console.error('Copy error:', err);
- this.showToast('Failed to copy text');
- });
- }
-
- insertIntoChatGpt(text) {
- const inputEl = document.querySelector(CONFIG.CHATGPT_INPUT_SELECTOR);
- if (inputEl) {
- inputEl.value = text;
- inputEl.dispatchEvent(new Event('input', { bubbles: true }));
- this.showToast('Prompt inserted');
- } else {
- this.showToast('ChatGPT input field not found');
- }
- }
-
- incrementUsage(id) {
- const item = this.prompts.find(p => p.id === id);
- if (!item) return;
- item.usageCount = (item.usageCount || 0) + 1;
- this.savePrompts();
- this.renderPrompts();
- }
-
- setRating(id, value) {
- const item = this.prompts.find(p => p.id === id);
- if (!item) return;
- item.rating = value;
- this.savePrompts();
- this.renderPrompts();
- }
-
- /***********************************************************************
- * Edit Title and Tag Management
- ***********************************************************************/
- toggleEditTitle(id, card) {
- const titleEl = card.querySelector('.prompt-title-display');
- const editIcon = card.querySelector('.prompt-edit-icon');
- const saveIcon = card.querySelector('.prompt-save-icon');
- const item = this.getItemById(id);
- if (!item) return;
-
- if (titleEl.contentEditable !== 'true') {
- // Enable editing; show title and tags
- card.classList.add('editing');
- titleEl.contentEditable = 'true';
- titleEl.textContent = item.title + (item.tags.length ? `, ${item.tags.join(', ')}` : '');
- titleEl.focus();
- // Place the cursor at the end
- const range = document.createRange();
- range.selectNodeContents(titleEl);
- range.collapse(false);
- const sel = window.getSelection();
- sel.removeAllRanges();
- sel.addRange(range);
- editIcon.style.display = 'none';
- saveIcon.style.display = 'inline-block';
- } else {
- // Save changes to title and tags
- const newText = titleEl.textContent.trim();
- if (!newText) {
- this.showToast('Title cannot be blank');
- return;
- }
- card.classList.remove('editing');
- titleEl.contentEditable = 'false';
- editIcon.style.display = 'inline-block';
- saveIcon.style.display = 'none';
- const [newTitle, ...tagParts] = newText.split(',');
- item.title = newTitle.trim();
- item.tags = tagParts.map(t => t.trim()).filter(t => t !== '');
- this.savePrompts();
- this.renderPrompts();
- }
- }
-
- getItemById(id) {
- return this.prompts.find(p => p.id === id) ||
- this.archivedPrompts.find(p => p.id === id);
- }
-
- /***********************************************************************
- * Clear All Prompts with Confirmation
- ***********************************************************************/
- handleClearAll(button) {
- if (button.classList.contains('confirm')) {
- this.prompts = [];
- this.archivedPrompts = [];
- this.savePrompts();
- this.saveArchivedPrompts();
- this.renderPrompts();
- this.showToast('All prompts cleared');
- button.textContent = 'Clear All';
- button.classList.remove('confirm');
- if (this.clearAllTimeout) {
- clearTimeout(this.clearAllTimeout);
- this.clearAllTimeout = null;
- }
- } else {
- button.textContent = 'Confirm Clear?';
- button.classList.add('confirm');
- if (this.clearAllTimeout) clearTimeout(this.clearAllTimeout);
- this.clearAllTimeout = setTimeout(() => {
- button.textContent = 'Clear All';
- button.classList.remove('confirm');
- this.clearAllTimeout = null;
- }, CONFIG.CONFIRM_TIMEOUT);
- }
- }
-
- /***********************************************************************
- * Render Tabs and Prompt List
- ***********************************************************************/
- renderTabs() {
- manager.querySelectorAll('.prompt-tab').forEach(tabEl => {
- tabEl.classList.toggle('active', tabEl.dataset.tab === this.activeTab);
- });
- }
-
- renderPrompts() {
- const content = manager.querySelector('.prompt-content');
- content.innerHTML = '';
-
- let itemsToRender = (this.activeTab === 'Archive')
- ? this.archivedPrompts
- : this.prompts.filter(p => p.category === this.activeTab);
-
- // Filter by search term and active tag
- itemsToRender = itemsToRender.filter(item => {
- const searchMatch = item.title.toLowerCase().includes(this.searchTerm) ||
- item.text.toLowerCase().includes(this.searchTerm);
- const tagMatch = this.activeTagFilter
- ? item.tags.map(t => t.toLowerCase()).includes(this.activeTagFilter)
- : true;
- return searchMatch && tagMatch;
- });
-
- // Sort: favorites first, then by rating and usage
- itemsToRender.sort((a, b) => {
- if (b.isFavorite !== a.isFavorite) return b.isFavorite ? 1 : -1;
- if (b.rating !== a.rating) return b.rating - a.rating;
- return b.usageCount - a.usageCount;
- });
-
- if (!itemsToRender.length) {
- content.innerHTML = `<div class="prompt-empty">
- ${(this.searchTerm || this.activeTagFilter) ? 'No matching prompts found' : 'No prompts saved yet'}
- </div>`;
- return;
- }
-
- // Build HTML for each prompt card.
- content.innerHTML = itemsToRender.map(item => {
- const stars = [1,2,3,4,5].map(value => {
- const filled = value <= item.rating ? 'filled' : '';
- return `<span class="prompt-rating-star ${filled}" data-value="${value}">★</span>`;
- }).join('');
-
- return `
- <div class="prompt-card ${item.isFavorite ? 'favorite' : ''}" data-id="${item.id}">
- <div class="prompt-title-wrapper">
- <button class="prompt-edit-icon">✎</button>
- <div class="prompt-title-display">${item.title}</div>
- <button class="prompt-save-icon" style="display:none;">💾</button>
- </div>
- <div class="prompt-preview">${item.text}</div>
- <div class="prompt-info">
- <span class="prompt-date">${new Date(item.date).toLocaleDateString()}</span>
- <br>
- <span class="prompt-url">[<a href="${item.url}" target="_blank">source</a>]</span>
- </div>
- <div class="prompt-tags">
- ${item.tags.map(tag => `<span>#${tag}</span>`).join('')}
- </div>
- <div class="prompt-actions">
- <div class="prompt-rating">${stars}</div>
- <button class="prompt-btn favorite">${item.isFavorite ? '★' : '☆'}</button>
- <button class="prompt-btn copy">Copy</button>
- <button class="prompt-btn insert">Insert</button>
- <button class="prompt-btn delete">Delete</button>
- ${this.activeTab === 'Archive'
- ? ''
- : `<button class="prompt-btn move-up">↑</button>
- <button class="prompt-btn move-down">↓</button>`
- }
- </div>
- </div>
- `;
- }).join('');
- }
-
- /***********************************************************************
- * Update Save Button Based on Selection
- ***********************************************************************/
- updateSaveButton() {
- const saveBtn = manager.querySelector('.prompt-save');
- if (!saveBtn) return;
- const selection = window.getSelection();
- saveBtn.disabled = !(selection && selection.toString().trim().length > 0);
- }
-
- /***********************************************************************
- * Open/Close Manager & Toast Notifications
- ***********************************************************************/
- toggle() {
- this.isOpen = !this.isOpen;
- manager.classList.toggle('open');
- // Do not hide the toggle icon once visible
- // (Remove 'hidden' class if present)
- toggleBtn.classList.remove('hidden');
- }
-
- close() {
- this.isOpen = false;
- manager.classList.remove('open');
- // Keep the toggle icon visible
- toggleBtn.classList.remove('hidden');
- }
-
- showToast(message) {
- const existing = document.querySelector('.prompt-toast');
- if (existing) existing.remove();
- const toast = document.createElement('div');
- toast.className = 'prompt-toast';
- toast.textContent = message;
- document.body.appendChild(toast);
- setTimeout(() => toast.remove(), CONFIG.TOAST_DURATION);
- }
-
- /***********************************************************************
- * Import/Export Functionality
- ***********************************************************************/
- importPrompts() {
- const fileInput = document.createElement('input');
- fileInput.type = 'file';
- fileInput.accept = 'application/json';
- fileInput.addEventListener('change', e => {
- const file = e.target.files[0];
- if (!file) return;
- const reader = new FileReader();
- reader.onload = evt => {
- try {
- const data = JSON.parse(evt.target.result);
- if (Array.isArray(data.prompts)) {
- data.prompts.forEach(item => this.prompts.push(item));
- }
- if (Array.isArray(data.archivedPrompts)) {
- data.archivedPrompts.forEach(item => this.archivedPrompts.push(item));
- }
- this.savePrompts();
- this.saveArchivedPrompts();
- this.renderPrompts();
- this.showToast('Prompts imported successfully');
- } catch (err) {
- console.error('Import error:', err);
- this.showToast('Failed to import file');
- }
- };
- reader.readAsText(file);
- });
- fileInput.click();
- }
-
- exportPrompts() {
- const data = {
- prompts: this.prompts,
- archivedPrompts: this.archivedPrompts
- };
- const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = 'gpt-prompts-export.json';
- document.body.appendChild(a);
- a.click();
- setTimeout(() => {
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- }, 0);
- }
- }
-
- // Instantiate the GPT Prompt Manager.
- new GptPromptManager();
-
- })();