Skip to content

Commit ac70a97

Browse files
committed
Queue functionality added
1 parent c76168c commit ac70a97

11 files changed

+313
-29
lines changed

buttons.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,19 @@ window.MaxExtensionButtons = {
103103
* @param {boolean} autoSend - Flag indicating whether to automatically send the message
104104
*/
105105
function processCustomSendButtonClick(event, customText, autoSend) {
106+
// Check if we are in queue mode in the floating panel.
107+
// This check must be first.
108+
if (window.MaxExtensionFloatingPanel && window.MaxExtensionFloatingPanel.isPanelVisible && globalMaxExtensionConfig.enableQueueMode) {
109+
const buttonConfig = {
110+
icon: event.target.innerHTML,
111+
text: customText,
112+
autoSend: autoSend
113+
};
114+
// Add to queue and stop further processing. The engine handles the rest.
115+
window.MaxExtensionFloatingPanel.addToQueue(buttonConfig);
116+
return;
117+
}
118+
106119
event.preventDefault();
107120
logConCgp('[buttons] Custom send button clicked');
108121

code-notes.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// code-notes.md
2+
23
# OneClickPrompts Chrome Extension - Codebase Overview
34

45
This document provides a high-level overview of the OneClickPrompts Chrome Extension codebase. It describes the purpose of each file and its role within the extension. Extension was previously called "ChatGPT Quick Buttons for your text".
@@ -87,10 +88,20 @@ The OneClickPrompts extension enhances AI chat platforms like ChatGPT, Claude, C
8788

8889
### `floating-panel-ui-queue.js`
8990

90-
- **Purpose:** Initializes the interactive elements within the floating panel's queue section.
91-
- **Role:** This script finds the pre-existing queue elements loaded from `floating-panel.html` and attaches the necessary JavaScript logic and event handlers. It no longer creates the DOM elements itself. Key functions:
91+
- **Purpose:** Initializes the interactive elements within the floating panel's queue section and handles rendering the queue's visual state.
92+
- **Role:** This script finds the pre-existing queue elements loaded from `floating-panel.html` and attaches the necessary JavaScript logic and event handlers. It is responsible for all direct DOM manipulation of the queue UI. Key functions:
9293
- `initializeQueueSection()`: Finds the queue toggle, delay input, and control buttons in the DOM and wires up their functionality.
93-
- **Dependencies:** `floating-panel.html` (relies on its structure), `interface.js` (uses `createToggle`).
94+
- `renderQueueDisplay()`: Clears and redraws the list of queued prompt icons.
95+
- `updateQueueControlsState()`: Manages the enabled/disabled state and icons of the play/pause and reset buttons.
96+
- **Dependencies:** `floating-panel.html`, `interface.js`, `floating-panel-queue-engine.js`.
97+
98+
### `floating-panel-ui-engine.js`
99+
100+
- **Purpose:** Contains the core state management and execution logic for the prompt queue.
101+
- **Role:** This script is UI-agnostic and manages the `promptQueue` array, the sending process, timers, and state flags (`isQueueRunning`). It provides the backend functionality for the queue feature. Key functions:
102+
- `addToQueue()`, `removeFromQueue()`, `startQueue()`, `pauseQueue()`, `resetQueue()`: Manage the queue's lifecycle.
103+
- `processNextQueueItem()`: The core loop that sends a prompt and sets a timer for the next.
104+
- **Dependencies:** `floating-panel.js`, `floating-panel-ui-queue.js` (calls UI update functions).
94105

95106
### `floating-panel-settings.js`
96107

@@ -139,7 +150,7 @@ The OneClickPrompts extension enhances AI chat platforms like ChatGPT, Claude, C
139150
### `buttons.js`
140151

141152
- **Purpose:** Manages the creation and functionality of custom send buttons.
142-
- **Role:** Creates button elements based on configuration, assigns keyboard shortcuts, and handles click events across different supported sites. It decides which site-specific functions are called.
153+
- **Role:** Creates button elements based on configuration, assigns keyboard shortcuts, and handles click events across different supported sites. It decides which site-specific functions are called. It also contains the logic to divert clicks to the prompt queue when Queue Mode is active in the floating panel.
143154

144155
### `buttons-init.js`
145156

@@ -253,12 +264,14 @@ The extension operates as follows:
253264
4. The content scripts retrieve the configuration from the service worker and inject the custom buttons into the page.
254265
5. Users can toggle between inline injected buttons and the floating panel via the toggle button (🔼).
255266
6. The floating panel's position, size, and visibility state are saved per website and restored when revisiting.
256-
7. When the user clicks a custom button (either in the inline container or floating panel), the appropriate site-specific function is called to insert the text and trigger the send button.
267+
7. When the user clicks a custom button:
268+
- If the floating panel is active and Queue Mode is on, the prompt is added to a queue. The queue sends prompts sequentially with a configurable delay.
269+
- Otherwise, the appropriate site-specific function is called to insert the text and trigger the send button.
257270

258271
## Additional Notes
259272

260273
- The extension uses a resilient injection mechanism to ensure that the custom buttons remain active even when the target website dynamically updates its content.
261274
- The `InjectionTargetsOnWebsite` class in `utils.js` centralizes the CSS selectors for different websites, making it easier to support new platforms.
262275
- The floating panel provides an alternative UI that can be positioned anywhere on the screen, offering flexibility for different workflows.
263276
- Button configurations are consistently applied between the inline injection and floating panel modes.
264-
- The extension uses debounced saving to prevent excessive storage writes when the user is dragging or resizing the floating panel.
277+
- The extension uses debounced saving to prevent excessive storage writes when the user is dragging or resizing the floating panel.

config.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,13 @@ async function getCurrentProfileConfig() {
230230
logConfigurationRelatedStuff(`Initialized missing 'customButtons' for profile: ${profileName}`);
231231
}
232232
// Ensure queue settings exist for backward compatibility
233-
if (typeof profile.queueDelaySeconds === 'undefined') {
234-
profile.queueDelaySeconds = 15; // Default delay in seconds
235-
logConfigurationRelatedStuff(`Initialized missing 'queueDelaySeconds' for profile: ${profileName}`);
233+
if (typeof profile.queueDelayMinutes === 'undefined') {
234+
profile.queueDelayMinutes = 5; // Default delay in minutes
235+
logConfigurationRelatedStuff(`Initialized missing 'queueDelayMinutes' for profile: ${profileName}`);
236+
}
237+
if (typeof profile.enableQueueMode === 'undefined') {
238+
profile.enableQueueMode = false;
239+
logConfigurationRelatedStuff(`Initialized missing 'enableQueueMode' for profile: ${profileName}`);
236240
}
237241
return profile;
238242
}
@@ -245,9 +249,13 @@ async function getCurrentProfileConfig() {
245249
defaultProfile.customButtons = [];
246250
logConfigurationRelatedStuff("Initialized missing 'customButtons' for default profile");
247251
}
248-
if (typeof defaultProfile.queueDelaySeconds === 'undefined') {
249-
defaultProfile.queueDelaySeconds = 15;
250-
logConfigurationRelatedStuff("Initialized missing 'queueDelaySeconds' for default profile");
252+
if (typeof defaultProfile.queueDelayMinutes === 'undefined') {
253+
defaultProfile.queueDelayMinutes = 5;
254+
logConfigurationRelatedStuff("Initialized missing 'queueDelayMinutes' for default profile");
255+
}
256+
if (typeof defaultProfile.enableQueueMode === 'undefined') {
257+
defaultProfile.enableQueueMode = false;
258+
logConfigurationRelatedStuff("Initialized missing 'enableQueueMode' for default profile");
251259
}
252260
return defaultProfile;
253261
} catch (error) {
@@ -534,5 +542,4 @@ chrome.storage.onChanged.addListener((changes, namespace) => {
534542
});
535543
}
536544
}
537-
});
538-
545+
});

default-config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"globalAutoSendEnabled": true,
55
"enableShortcuts": true,
66
"firstModificationDone": false,
7+
"enableQueueMode": false,
8+
"queueDelayMinutes": 5,
79
"customButtons": [
810
{
911
"icon": "🧠",

floating-panel-ui-engine.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Version: 1.0
2+
//
3+
// Documentation:
4+
// This file contains the core engine logic for the prompt queue feature.
5+
// It manages the queue's state, adding/removing items, and the sequential
6+
// sending process with delays. It is designed to be UI-agnostic.
7+
// All functions extend the window.MaxExtensionFloatingPanel namespace.
8+
//
9+
// Methods included:
10+
// - addToQueue(buttonConfig): Adds a prompt to the queue.
11+
// - removeFromQueue(index): Removes a prompt from the queue by its index.
12+
// - startQueue(): Begins the sequential sending process.
13+
// - pauseQueue(): Pauses the sending process.
14+
// - resetQueue(): Stops and clears the entire queue.
15+
// - processNextQueueItem(): The core function that sends one item and sets a timer for the next.
16+
// - getSiteSpecificSendFunction(): A helper to determine which site-specific send function to use.
17+
//
18+
// Dependencies:
19+
// - floating-panel.js: Provides the namespace and shared properties.
20+
// - floating-panel-ui-queue.js: Provides UI update functions like renderQueueDisplay.
21+
22+
'use strict';
23+
24+
/**
25+
* Adds a prompt configuration to the queue.
26+
* @param {object} buttonConfig - The configuration of the button clicked.
27+
*/
28+
window.MaxExtensionFloatingPanel.addToQueue = function (buttonConfig) {
29+
if (this.promptQueue.length >= this.QUEUE_MAX_SIZE) {
30+
logConCgp('[queue-engine] Queue is full. Cannot add more prompts.');
31+
// Optional: Add visual feedback for the user
32+
if (this.queueDisplayArea) {
33+
this.queueDisplayArea.style.borderColor = 'red';
34+
setTimeout(() => {
35+
this.queueDisplayArea.style.borderColor = '';
36+
}, 500);
37+
}
38+
return;
39+
}
40+
41+
this.promptQueue.push(buttonConfig);
42+
logConCgp('[queue-engine] Added to queue:', buttonConfig.text);
43+
this.renderQueueDisplay();
44+
this.updateQueueControlsState();
45+
};
46+
47+
/**
48+
* Removes a prompt from the queue at a specific index.
49+
* @param {number} index - The index of the item to remove.
50+
*/
51+
window.MaxExtensionFloatingPanel.removeFromQueue = function (index) {
52+
if (index > -1 && index < this.promptQueue.length) {
53+
const removed = this.promptQueue.splice(index, 1);
54+
logConCgp('[queue-engine] Removed from queue:', removed[0].text);
55+
this.renderQueueDisplay();
56+
this.updateQueueControlsState();
57+
}
58+
};
59+
60+
/**
61+
* Starts the queue processing.
62+
*/
63+
window.MaxExtensionFloatingPanel.startQueue = function () {
64+
if (this.isQueueRunning || this.promptQueue.length === 0) {
65+
return;
66+
}
67+
this.isQueueRunning = true;
68+
logConCgp('[queue-engine] Queue started.');
69+
this.updateQueueControlsState();
70+
this.processNextQueueItem();
71+
};
72+
73+
/**
74+
* Pauses the queue processing.
75+
*/
76+
window.MaxExtensionFloatingPanel.pauseQueue = function () {
77+
this.isQueueRunning = false;
78+
if (this.queueTimerId) {
79+
clearTimeout(this.queueTimerId);
80+
this.queueTimerId = null;
81+
}
82+
logConCgp('[queue-engine] Queue paused.');
83+
this.updateQueueControlsState();
84+
};
85+
86+
/**
87+
* Resets the queue, clearing all items and stopping the process.
88+
*/
89+
window.MaxExtensionFloatingPanel.resetQueue = function () {
90+
this.pauseQueue(); // Stop any running timers and set isQueueRunning to false
91+
this.promptQueue = [];
92+
logConCgp('[queue-engine] Queue reset.');
93+
this.renderQueueDisplay();
94+
this.updateQueueControlsState(); // This will disable buttons as needed
95+
};
96+
97+
/**
98+
* Processes the next item in the queue.
99+
*/
100+
window.MaxExtensionFloatingPanel.processNextQueueItem = function () {
101+
if (!this.isQueueRunning) {
102+
return;
103+
}
104+
105+
if (this.promptQueue.length === 0) {
106+
logConCgp('[queue-engine] Queue is empty. Stopping.');
107+
this.pauseQueue(); // Effectively stops and resets the UI
108+
return;
109+
}
110+
111+
const item = this.promptQueue.shift(); // Get the first item and remove it
112+
this.renderQueueDisplay(); // Update UI to show the item is gone
113+
logConCgp(`[queue-engine] Sending item:`, item.text);
114+
115+
const sendFunction = this.getSiteSpecificSendFunction();
116+
if (sendFunction) {
117+
// A mock event object is sufficient for the send functions
118+
const mockEvent = { preventDefault: () => { } };
119+
sendFunction(mockEvent, item.text, item.autoSend);
120+
} else {
121+
logConCgp('[queue-engine] No send function found for this site. Stopping queue.');
122+
this.resetQueue();
123+
return;
124+
}
125+
126+
// If there are more items, set a timeout for the next one
127+
if (this.promptQueue.length > 0) {
128+
const delayMs = (globalMaxExtensionConfig.queueDelayMinutes || 5) * 60 * 1000;
129+
logConCgp(`[queue-engine] Waiting for ${delayMs / 1000 / 60} minutes before next item.`);
130+
this.queueTimerId = setTimeout(() => this.processNextQueueItem(), delayMs);
131+
} else {
132+
logConCgp('[queue-engine] All items have been sent.');
133+
this.pauseQueue(); // Queue finished, so pause/stop
134+
}
135+
};
136+
137+
/**
138+
* Helper to get the site-specific send function.
139+
* @returns {Function|null} The send function for the current site or null.
140+
*/
141+
window.MaxExtensionFloatingPanel.getSiteSpecificSendFunction = function () {
142+
const activeSite = window.InjectionTargetsOnWebsite.activeSite;
143+
switch (activeSite) {
144+
case 'ChatGPT': return window.processChatGPTCustomSendButtonClick;
145+
case 'Claude': return window.processClaudeCustomSendButtonClick;
146+
case 'Copilot': return window.processCopilotCustomSendButtonClick;
147+
case 'DeepSeek': return window.processDeepSeekCustomSendButtonClick;
148+
case 'AIStudio': return window.processAIStudioCustomSendButtonClick;
149+
case 'Grok': return window.processGrokCustomSendButtonClick;
150+
case 'Gemini': return window.processGeminiCustomSendButtonClick;
151+
default:
152+
logConCgp(`[queue-engine] Unsupported site for queue: ${activeSite}`);
153+
return null;
154+
}
155+
};

floating-panel-ui-queue.js

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
//
99
// Methods included:
1010
// - initializeQueueSection(): Wires up the DOM structure for the queue UI.
11+
// - renderQueueDisplay(): Updates the visual display of queued items.
12+
// - updateQueueControlsState(): Manages the state of play/pause/reset buttons.
1113
//
1214
// Dependencies:
1315
// - floating-panel.js: Provides the namespace and shared properties.
@@ -31,7 +33,7 @@ window.MaxExtensionFloatingPanel.initializeQueueSection = function () {
3133
this.queueDisplayArea = document.getElementById('max-extension-queue-display');
3234

3335
if (!this.queueSectionElement) {
34-
logConCgp('[floating-panel] Queue section element not found in the DOM.');
36+
logConCgp('[floating-panel-queue] Queue section element not found in the DOM.');
3537
return;
3638
}
3739

@@ -41,13 +43,16 @@ window.MaxExtensionFloatingPanel.initializeQueueSection = function () {
4143
});
4244

4345
// Set initial values and event listeners for controls
44-
this.delayInputElement.value = globalMaxExtensionConfig.queueDelaySeconds || 15;
46+
this.delayInputElement.value = globalMaxExtensionConfig.queueDelayMinutes || 5;
4547
this.delayInputElement.addEventListener('change', (event) => {
46-
const delay = parseInt(event.target.value, 10);
47-
if (!isNaN(delay) && delay >= 0) {
48-
globalMaxExtensionConfig.queueDelaySeconds = delay;
49-
// The config will be saved on the next profile save action
48+
let delay = parseInt(event.target.value, 10);
49+
if (isNaN(delay) || delay < 2) {
50+
delay = 2; // Enforce minimum delay
51+
event.target.value = delay;
5052
}
53+
globalMaxExtensionConfig.queueDelayMinutes = delay;
54+
logConCgp(`[floating-panel-queue] Queue delay set to ${delay} minutes.`);
55+
// Note: The config is saved with the profile, or upon panel settings save.
5156
});
5257

5358
// Create and insert the Queue Mode toggle
@@ -63,10 +68,69 @@ window.MaxExtensionFloatingPanel.initializeQueueSection = function () {
6368
}
6469
);
6570
this.queueModeToggle.style.margin = '0';
71+
this.queueModeToggle.querySelector('label').style.fontSize = '12px';
6672
this.queueModeToggle.title = 'When enabled, clicking buttons adds them to a queue instead of sending immediately.';
6773
togglePlaceholder.appendChild(this.queueModeToggle);
6874

6975
// Set initial visibility of controls based on config
7076
expandableSection.style.display = isQueueEnabled ? 'contents' : 'none';
7177
this.queueDisplayArea.style.display = isQueueEnabled ? 'flex' : 'none';
78+
79+
// Attach event listeners to buttons
80+
this.playQueueButton.addEventListener('click', () => {
81+
if (this.isQueueRunning) {
82+
this.pauseQueue();
83+
} else {
84+
this.startQueue();
85+
}
86+
});
87+
88+
this.resetQueueButton.addEventListener('click', () => {
89+
this.resetQueue();
90+
});
91+
92+
// Initial state update for controls
93+
this.updateQueueControlsState();
94+
};
95+
96+
/**
97+
* Renders the queue display area with the current items in the queue.
98+
*/
99+
window.MaxExtensionFloatingPanel.renderQueueDisplay = function () {
100+
if (!this.queueDisplayArea) return;
101+
102+
this.queueDisplayArea.innerHTML = ''; // Clear previous items
103+
this.promptQueue.forEach((item, index) => {
104+
const queuedItemElement = document.createElement('button');
105+
queuedItemElement.className = 'max-extension-queued-item';
106+
queuedItemElement.innerHTML = item.icon;
107+
queuedItemElement.title = `Click to remove: ${item.text}`;
108+
queuedItemElement.addEventListener('click', () => {
109+
this.removeFromQueue(index);
110+
});
111+
this.queueDisplayArea.appendChild(queuedItemElement);
112+
});
113+
};
114+
115+
/**
116+
* Updates the state (icon, disabled status) of the queue control buttons.
117+
*/
118+
window.MaxExtensionFloatingPanel.updateQueueControlsState = function () {
119+
if (!this.playQueueButton || !this.resetQueueButton) return;
120+
121+
const hasItems = this.promptQueue.length > 0;
122+
123+
// Play/Pause Button
124+
if (this.isQueueRunning) {
125+
this.playQueueButton.innerHTML = '⏸️'; // Pause icon
126+
this.playQueueButton.title = 'Pause the queue.';
127+
this.playQueueButton.disabled = false;
128+
} else {
129+
this.playQueueButton.innerHTML = '▶️'; // Play icon
130+
this.playQueueButton.title = 'Start sending the queued prompts.';
131+
this.playQueueButton.disabled = !hasItems; // Disabled if no items
132+
}
133+
134+
// Reset Button
135+
this.resetQueueButton.disabled = !hasItems && !this.isQueueRunning;
72136
};

0 commit comments

Comments
 (0)