Skip to content

Commit dc07ea7

Browse files
committed
fixed respawn of traditional buttons
1 parent 71f3913 commit dc07ea7

File tree

6 files changed

+94
-66
lines changed

6 files changed

+94
-66
lines changed

buttons-init.js

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// buttons-init.js
21
// Version: 1.0
32
//
43
// Documentation:
@@ -24,7 +23,7 @@ window.MaxExtensionButtonsInit = {
2423
* Creates and appends toggle switches to the specified container.
2524
* @param {HTMLElement} container - The DOM element to which toggles will be appended.
2625
*/
27-
generateAndAppendToggles: function(container) {
26+
generateAndAppendToggles: function (container) {
2827
const autoSendToggle = MaxExtensionInterface.createToggle(
2928
'auto-send-toggle',
3029
'Enable Auto-send',
@@ -51,13 +50,14 @@ window.MaxExtensionButtonsInit = {
5150
/**
5251
* Creates and appends custom send buttons to the specified container.
5352
* @param {HTMLElement} container - The DOM element to which custom buttons will be appended.
53+
* @param {boolean} isPanel - Flag indicating if the container is the floating panel.
5454
*/
55-
generateAndAppendCustomSendButtons: function(container) {
56-
// Add floating panel toggle button at the beginning
57-
if (window.MaxExtensionFloatingPanel) {
55+
generateAndAppendCustomSendButtons: function (container, isPanel) {
56+
// Only add the toggle button to the INLINE container, not the floating panel itself.
57+
if (window.MaxExtensionFloatingPanel && !isPanel) {
5858
const floatingPanelToggleButton = window.MaxExtensionFloatingPanel.createPanelToggleButton();
5959
container.appendChild(floatingPanelToggleButton);
60-
logConCgp('[init] Floating panel toggle button has been created and appended.');
60+
logConCgp('[init] Floating panel toggle button has been created and appended for inline container.');
6161
}
6262

6363
globalMaxExtensionConfig.customButtons.forEach((buttonConfiguration, index) => {
@@ -77,7 +77,7 @@ window.MaxExtensionButtonsInit = {
7777
* Creates and inserts custom buttons and toggles into the target container element.
7878
* @param {HTMLElement} targetContainer - The DOM element where custom elements will be inserted.
7979
*/
80-
createAndInsertCustomElements: function(targetContainer) {
80+
createAndInsertCustomElements: function (targetContainer) {
8181
// Prevent duplication by checking if the container already exists using dynamic selector
8282
const existingContainer = document.getElementById(window.InjectionTargetsOnWebsite.selectors.buttonsContainerId);
8383
if (existingContainer && existingContainer.parentElement === targetContainer) {
@@ -98,44 +98,47 @@ window.MaxExtensionButtonsInit = {
9898
z-index: 1000;
9999
`;
100100

101-
// Append custom send buttons
102-
this.generateAndAppendCustomSendButtons(customElementsContainer);
101+
// Determine if we are creating buttons for the panel or for an inline container.
102+
const isPanel = targetContainer.id === 'max-extension-floating-panel-content';
103+
104+
// Append custom send buttons, passing the context.
105+
this.generateAndAppendCustomSendButtons(customElementsContainer, isPanel);
103106
// Append toggle switches
104107
this.generateAndAppendToggles(customElementsContainer);
105108

106109
targetContainer.appendChild(customElementsContainer);
107110
logConCgp('[init] Custom elements have been inserted into the DOM.');
108111
},
109-
112+
110113
/**
111114
* Updates all buttons and toggles in response to a profile change.
112115
* This refreshes both the floating panel and the original container.
113116
*/
114-
updateButtonsForProfileChange: function() {
117+
updateButtonsForProfileChange: function () {
115118
// Update buttons in the original container
116119
const originalContainer = document.getElementById(window.InjectionTargetsOnWebsite.selectors.buttonsContainerId);
117120
if (originalContainer) {
118121
// Clear existing buttons and toggles
119122
originalContainer.innerHTML = '';
120-
123+
121124
// Re-generate buttons and toggles with new profile data
122-
this.generateAndAppendCustomSendButtons(originalContainer);
125+
this.generateAndAppendCustomSendButtons(originalContainer, false); // Not panel
123126
this.generateAndAppendToggles(originalContainer);
124-
127+
125128
logConCgp('[init] Updated buttons in original container for profile change.');
126129
}
127-
130+
128131
// Update buttons in the floating panel if it exists and is initialized
129132
if (window.MaxExtensionFloatingPanel && window.MaxExtensionFloatingPanel.panelElement) {
130133
const panelContent = document.getElementById('max-extension-floating-panel-content');
131134
if (panelContent) {
132135
// Clear existing buttons and toggles
133136
panelContent.innerHTML = '';
134-
137+
135138
// Re-generate buttons and toggles with new profile data
136-
this.generateAndAppendCustomSendButtons(panelContent);
139+
this.generateAndAppendCustomSendButtons(panelContent, true); // This is the panel
137140
this.generateAndAppendToggles(panelContent);
138-
141+
139142
logConCgp('[init] Updated buttons in floating panel for profile change.');
140143
}
141144
}
@@ -146,13 +149,13 @@ window.MaxExtensionButtonsInit = {
146149
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
147150
if (message.type === 'profileChanged') {
148151
logConCgp('[init] Received profile change notification');
149-
152+
150153
// Update the global config with the new profile data
151154
window.globalMaxExtensionConfig = message.config;
152-
155+
153156
// Update the UI components
154157
window.MaxExtensionButtonsInit.updateButtonsForProfileChange();
155-
158+
156159
// Acknowledge the message
157160
sendResponse({ success: true });
158161
}

code-notes.md

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// code-notes.md
2-
31
# OneClickPrompts Chrome Extension - Codebase Overview
42

53
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".
@@ -8,6 +6,16 @@ This document provides a high-level overview of the OneClickPrompts Chrome Exten
86

97
The OneClickPrompts extension enhances AI chat platforms like ChatGPT, Claude, Copilot, DeepSeek, AI Studio, Gemini, and Grok by adding customizable buttons that inject pre-defined prompts into the chat input area. It also includes features for managing profiles, enabling keyboard shortcuts, and customizing the user interface.
108

9+
## Core Reliability Pattern: Crash-Only / Self-Healing DOM Injection
10+
11+
A foundational architectural decision in this extension is the use of a **"Crash-only"** (or **"Self-healing"**) reliability pattern for managing the UI in the dynamic, and often hostile, DOM of the target Single-Page Applications (SPAs).
12+
13+
- **The Problem:** The DOM on sites like ChatGPT is unpredictable. Elements are frequently destroyed and re-created, making simple, "surgical" DOM manipulations fragile. An attempt to insert a button can fail if the target container disappears at the wrong millisecond.
14+
- **The Solution:** Instead of writing complex recovery code for every possible failure, the extension embraces a "let-it-crash" philosophy. Any time the UI's integrity is in question (e.g., after an SPA navigation or when closing the floating panel), the extension triggers a full, **idempotent re-initialization** of its UI components.
15+
- **Why It Works:** This approach delegates UI creation to the robust resiliency engine in `buttons-injection.js`, which is designed to patiently wait for injection targets and handle dynamic changes. This trades a small amount of computational efficiency for a massive gain in reliability, ensuring the extension's UI is always present and functional. This pattern is analogous to forcing a re-mount in frameworks like React by changing a component's `key`.
16+
17+
This "Crash-only" principle informs the design of `init.js`, `buttons-injection.js`, and `floating-panel-ui-interaction.js`.
18+
1119
## File Descriptions
1220

1321
### `manifest.json`
@@ -80,7 +88,7 @@ The OneClickPrompts extension enhances AI chat platforms like ChatGPT, Claude, C
8088
- **Purpose:** Contains UI interaction and state management methods for the floating panel.
8189
- **Role:** Handles toggling panel visibility and updating the panel appearance from settings. The old "hide and clone" mechanism has been completely replaced.
8290
- **Key functions:**
83-
- `togglePanel()`: An `async` function that toggles the floating panel's visibility using a **"destroy and re-create"** mechanism. When toggled ON, it destroys the inline buttons and re-creates them inside the panel. When toggled OFF, it does the reverse.
91+
- `togglePanel()`: An `async` function that toggles the floating panel's visibility. When closed, it intentionally triggers the extension's main `publicStaticVoidMain()` initializer, delegating the button re-creation to the robust **"Crash-only"** resiliency engine.
8492
- `updatePanelFromSettings()`: Updates the panel’s dynamic styles (like position, size, and opacity).
8593

8694
### `floating-panel-ui-queue.js`
@@ -137,8 +145,8 @@ The OneClickPrompts extension enhances AI chat platforms like ChatGPT, Claude, C
137145

138146
### `init.js`
139147

140-
- **Purpose:** Main initialization script for the content script. It acts as the **Director** of the initial UI setup.
141-
- **Role:** Implements a **"decide first, then create"** architecture to prevent UI flicker. It asynchronously checks if the floating panel should be visible for the current site *before* rendering any buttons. Based on this setting, it either injects the buttons into the traditional inline location (via `buttons-injection.js`) or directly into the floating panel, which it creates on demand. It also orchestrates the initialization of the full floating panel system in a controlled sequence to prevent race conditions.
148+
- **Purpose:** Main initialization script for the content script. It acts as the **Director** of the initial UI setup and embodies the **Crash-only** philosophy.
149+
- **Role:** Implements a **"decide first, then create"** architecture to prevent UI flicker. It asynchronously checks if the floating panel should be visible for the current site _before_ rendering any buttons. Based on this setting, it either injects the buttons into the traditional inline location (via `buttons-injection.js`) or directly into the floating panel. Its main initialization function (`publicStaticVoidMain`) is designed to be **idempotent** and is called on page load, SPA navigation, and panel closing to ensure a consistent and reliable UI state.
142150

143151
### `interface.js`
144152

@@ -157,8 +165,8 @@ The OneClickPrompts extension enhances AI chat platforms like ChatGPT, Claude, C
157165

158166
### `buttons-injection.js`
159167

160-
- **Purpose:** Handles the injection of custom buttons into the webpage for the **inline mode**.
161-
- **Role:** This script is now primarily used when `init.js` decides that the floating panel should be hidden on initial load. It finds the correct injection point on the page and implements a resiliency mechanism to re-inject the elements if they disappear due to dynamic page updates.
168+
- **Purpose:** Handles the injection of custom buttons into the webpage for the **inline mode** and provides the core resiliency mechanism.
169+
- **Role:** This script is now primarily used when `init.js` decides that the floating panel should be hidden on initial load. It finds the correct injection point on the page and implements a **self-healing** resiliency mechanism (using `MutationObserver` and timeouts) to re-inject the elements if they disappear due to dynamic page updates.
162170

163171
### `buttons-clicking-chatgpt.js`, `buttons-clicking-claude.js`, `buttons-clicking-copilot.js`, `buttons-clicking-deepseek.js`, `buttons-clicking-aistudio.js`, `buttons-clicking-grok.js`, `buttons-clicking-gemini.js`
164172

@@ -258,11 +266,10 @@ The extension operates as follows:
258266

259267
1. The user configures the extension through the popup interface (`popup.html` and `popup-page-scripts/*`).
260268
2. The configuration is stored in Chrome's storage by the service worker (`config.js`).
261-
3. When the user visits a supported website, the content scripts are injected into the page.
262-
4. The main content script (`init.js`) first checks if the floating panel should be visible. It then creates the custom buttons directly in the correct location (either inline or inside the newly created floating panel) to prevent any visual flicker.
263-
5. When the user toggles the panel's visibility, the buttons are destroyed from their current location and re-created in the new one.
264-
6. The floating panel's position, size, and visibility state are saved per website and restored when revisiting.
265-
7. When the user clicks a custom button:
269+
3. When the user visits a supported website, the content scripts are injected. The main initializer (`init.js`) then decides whether to create the UI inline or in the floating panel, based on saved settings, preventing any visual flicker.
270+
4. If the UI disappears due to SPA updates, the **self-healing** resiliency engine in `buttons-injection.js` detects the change and triggers a full, idempotent re-initialization.
271+
5. When the user closes the floating panel, this also triggers the same idempotent re-initialization, reliably moving the buttons back to their inline location.
272+
6. When the user clicks a custom button:
266273
- 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.
267274
- Otherwise, the appropriate site-specific function is called to insert the text and trigger the send button.
268275

@@ -272,4 +279,4 @@ The extension operates as follows:
272279
- The `InjectionTargetsOnWebsite` class in `utils.js` centralizes the CSS selectors for different websites, making it easier to support new platforms.
273280
- The floating panel provides an alternative UI that can be positioned anywhere on the screen, offering flexibility for different workflows.
274281
- Button configurations are consistently applied between the inline injection and floating panel modes.
275-
- The extension uses debounced saving to prevent excessive storage writes when the user is dragging or resizing the floating panel.
282+
- The extension uses debounced saving to prevent excessive storage writes when the user is dragging or resizing the floating panel.

floating-panel-settings.js

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,24 +93,36 @@ window.MaxExtensionFloatingPanel.loadPanelSettings = function () {
9393

9494
/**
9595
* Saves panel settings to Chrome's storage via the config service worker.
96+
* Returns a Promise that resolves on success and rejects on failure.
9697
*/
9798
window.MaxExtensionFloatingPanel.savePanelSettings = function () {
98-
try {
99-
const hostname = window.location.hostname;
100-
chrome.runtime.sendMessage({
101-
type: 'saveFloatingPanelSettings',
102-
hostname: hostname,
103-
settings: this.currentPanelSettings
104-
}, (response) => {
105-
if (chrome.runtime.lastError) {
106-
logConCgp('[floating-panel] Failed to save panel settings:', chrome.runtime.lastError.message);
107-
} else if (response && response.success) {
108-
logConCgp('[floating-panel] Saved panel settings for ' + hostname);
109-
}
110-
});
111-
} catch (error) {
112-
logConCgp('[floating-panel] Error saving panel settings: ' + error.message);
113-
}
99+
return new Promise((resolve, reject) => {
100+
try {
101+
const hostname = window.location.hostname;
102+
chrome.runtime.sendMessage({
103+
type: 'saveFloatingPanelSettings',
104+
hostname: hostname,
105+
settings: this.currentPanelSettings
106+
}, (response) => {
107+
if (chrome.runtime.lastError) {
108+
const errorMsg = `Failed to save panel settings: ${chrome.runtime.lastError.message}`;
109+
logConCgp(`[floating-panel] ${errorMsg}`);
110+
reject(new Error(errorMsg));
111+
} else if (response && response.success) {
112+
logConCgp('[floating-panel] Saved panel settings for ' + hostname);
113+
resolve();
114+
} else {
115+
const errorMsg = `Save failed with response: ${JSON.stringify(response)}`;
116+
logConCgp(`[floating-panel] ${errorMsg}`);
117+
reject(new Error(errorMsg));
118+
}
119+
});
120+
} catch (error) {
121+
const errorMsg = `Error saving panel settings: ${error.message}`;
122+
logConCgp(`[floating-panel] ${errorMsg}`);
123+
reject(error);
124+
}
125+
});
114126
};
115127

116128
/**

floating-panel-ui-interaction.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,16 @@ window.MaxExtensionFloatingPanel.togglePanel = async function (event) {
3434

3535
this.isPanelVisible = !this.isPanelVisible;
3636
this.currentPanelSettings.isVisible = this.isPanelVisible;
37-
this.debouncedSavePanelSettings();
37+
38+
// Await the save to ensure state is persisted before re-initializing. This is critical.
39+
await this.savePanelSettings();
3840

3941
if (this.isPanelVisible) {
4042
logConCgp('[floating-panel] Toggling panel ON. Re-creating buttons in panel.');
41-
// 1. Destroy the inline buttons container. This prevents resiliency checks from finding an empty container.
43+
// 1. Destroy the inline buttons container.
4244
const originalContainer = document.getElementById(window.InjectionTargetsOnWebsite.selectors.buttonsContainerId);
4345
if (originalContainer) {
4446
originalContainer.remove();
45-
logConCgp('[floating-panel] Destroyed inline buttons container.');
4647
}
4748

4849
// 2. Create buttons directly in the panel.
@@ -61,26 +62,25 @@ window.MaxExtensionFloatingPanel.togglePanel = async function (event) {
6162
}
6263

6364
} else {
64-
logConCgp('[floating-panel] Toggling panel OFF. Re-creating buttons inline.');
65+
logConCgp('[floating-panel] Toggling panel OFF. Re-initializing extension for inline buttons.');
6566
// 1. Destroy buttons inside the panel.
6667
const panelContent = document.getElementById('max-extension-floating-panel-content');
6768
if (panelContent) {
6869
panelContent.innerHTML = '';
69-
logConCgp('[floating-panel] Destroyed panel buttons.');
7070
}
7171

7272
// 2. Hide the panel.
7373
this.panelElement.style.display = 'none';
7474

75-
// 3. Perform a fast, direct re-creation of buttons in their original inline location.
76-
// The flag we set prevents the watchdog from interfering with this.
77-
const selectors = window.InjectionTargetsOnWebsite.selectors.containers;
78-
MaxExtensionUtils.waitForElements(selectors, (targetDiv) => {
79-
logConCgp('[floating-panel] Found inline target. Injecting buttons directly.');
80-
window.MaxExtensionButtonsInit.createAndInsertCustomElements(targetDiv);
81-
});
75+
// 3. Re-run the entire, robust initialization script.
76+
// This will correctly detect that the panel is now hidden (since we just saved that setting)
77+
// and will proceed with the standard, resilient inline injection.
78+
publicStaticVoidMain();
8279
}
83-
} finally {
80+
} catch (error) {
81+
logConCgp('[floating-panel] CRITICAL ERROR in togglePanel try block:', error);
82+
}
83+
finally {
8484
// Re-enable the watchdog after a short delay to allow the DOM to settle.
8585
setTimeout(() => {
8686
window.OneClickPrompts_isTogglingPanel = false;

0 commit comments

Comments
 (0)