Skip to content

Commit 01e4ae1

Browse files
committed
cross-chat functionality
1 parent 2651bb2 commit 01e4ae1

File tree

4 files changed

+212
-38
lines changed

4 files changed

+212
-38
lines changed

buttons-init.js

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,63 @@ window.MaxExtensionButtonsInit = {
5252
* @param {HTMLElement} container - The DOM element to which custom buttons will be appended.
5353
* @param {boolean} isPanel - Flag indicating if the container is the floating panel.
5454
*/
55-
generateAndAppendCustomSendButtons: function (container, isPanel) {
56-
// Only add the toggle button to the INLINE container, not the floating panel itself.
55+
generateAndAppendAllButtons: function (container, isPanel) {
56+
// --- Create a unified list of all buttons to be rendered ---
57+
const allButtonDefs = [];
58+
let nonSeparatorCount = 0;
59+
60+
// 1. Add Cross-Chat buttons if they should be placed 'before'
61+
if (window.globalCrossChatConfig?.enabled && window.globalCrossChatConfig.placement === 'before') {
62+
allButtonDefs.push({ type: 'copy' });
63+
allButtonDefs.push({ type: 'paste' });
64+
}
65+
66+
// 2. Add standard custom buttons
67+
globalMaxExtensionConfig.customButtons.forEach(config => {
68+
allButtonDefs.push({ type: 'custom', config: config });
69+
});
70+
71+
// 3. Add Cross-Chat buttons if they should be placed 'after'
72+
if (window.globalCrossChatConfig?.enabled && window.globalCrossChatConfig.placement === 'after') {
73+
allButtonDefs.push({ type: 'copy' });
74+
allButtonDefs.push({ type: 'paste' });
75+
}
76+
77+
// --- Render all buttons from the unified list ---
78+
79+
// Add floating panel toggle first, if applicable
5780
if (window.MaxExtensionFloatingPanel && !isPanel) {
5881
const floatingPanelToggleButton = window.MaxExtensionFloatingPanel.createPanelToggleButton();
5982
container.appendChild(floatingPanelToggleButton);
6083
logConCgp('[init] Floating panel toggle button has been created and appended for inline container.');
6184
}
6285

63-
globalMaxExtensionConfig.customButtons.forEach((buttonConfiguration, index) => {
64-
if (buttonConfiguration.separator) {
86+
// Process the unified list to create and append buttons
87+
allButtonDefs.forEach((def, index) => {
88+
// Handle separators from custom buttons
89+
if (def.type === 'custom' && def.config.separator) {
6590
const separatorElement = MaxExtensionUtils.createSeparator();
6691
container.appendChild(separatorElement);
6792
logConCgp('[init] Separator element has been created and appended.');
68-
} else {
69-
const customSendButton = MaxExtensionButtons.createCustomSendButton(buttonConfiguration, index, processCustomSendButtonClick);
70-
container.appendChild(customSendButton);
71-
logConCgp(`[init] Custom send button ${index + 1} has been created:`, customSendButton);
93+
return; // Skip to next item
94+
}
95+
96+
// Assign a shortcut key if enabled and available
97+
let shortcutKey = null;
98+
if (globalMaxExtensionConfig.enableShortcuts && nonSeparatorCount < 10) {
99+
shortcutKey = nonSeparatorCount + 1;
72100
}
101+
102+
let buttonElement;
103+
if (def.type === 'copy' || def.type === 'paste') {
104+
buttonElement = MaxExtensionButtons.createCrossChatButton(def.type, shortcutKey);
105+
} else { // 'custom'
106+
buttonElement = MaxExtensionButtons.createCustomSendButton(def.config, index, processCustomSendButtonClick, shortcutKey);
107+
}
108+
109+
container.appendChild(buttonElement);
110+
nonSeparatorCount++;
111+
logConCgp(`[init] Button ${nonSeparatorCount} (${def.type}) has been created and appended.`);
73112
});
74113
},
75114

@@ -102,7 +141,7 @@ window.MaxExtensionButtonsInit = {
102141
const isPanel = targetContainer.id === 'max-extension-floating-panel-content';
103142

104143
// Append custom send buttons, passing the context.
105-
this.generateAndAppendCustomSendButtons(customElementsContainer, isPanel);
144+
this.generateAndAppendAllButtons(customElementsContainer, isPanel);
106145
// Append toggle switches
107146
this.generateAndAppendToggles(customElementsContainer);
108147

@@ -121,8 +160,7 @@ window.MaxExtensionButtonsInit = {
121160
// Clear existing buttons and toggles
122161
originalContainer.innerHTML = '';
123162

124-
// Re-generate buttons and toggles with new profile data
125-
this.generateAndAppendCustomSendButtons(originalContainer, false); // Not panel
163+
this.generateAndAppendAllButtons(originalContainer, false); // Not panel
126164
this.generateAndAppendToggles(originalContainer);
127165

128166
logConCgp('[init] Updated buttons in original container for profile change.');
@@ -135,8 +173,7 @@ window.MaxExtensionButtonsInit = {
135173
// Clear existing buttons and toggles
136174
panelContent.innerHTML = '';
137175

138-
// Re-generate buttons and toggles with new profile data
139-
this.generateAndAppendCustomSendButtons(panelContent, true); // This is the panel
176+
this.generateAndAppendAllButtons(panelContent, true); // This is the panel
140177
this.generateAndAppendToggles(panelContent);
141178

142179
logConCgp('[init] Updated buttons in floating panel for profile change.');
@@ -152,6 +189,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
152189

153190
// Update the global config with the new profile data
154191
window.globalMaxExtensionConfig = message.config;
192+
// Note: Cross-chat config is global and does not change with profile.
155193

156194
// Update the UI components
157195
window.MaxExtensionButtonsInit.updateButtonsForProfileChange();

buttons.js

Lines changed: 123 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,139 @@
2727
* Namespace object containing functions related to creating and managing custom buttons.
2828
*/
2929
window.MaxExtensionButtons = {
30+
/**
31+
* Creates a cross-chat prompt sharing button ('Copy' or 'Paste').
32+
* @param {string} type - The type of button, either 'copy' or 'paste'.
33+
* @param {number|null} shortcutKey - The shortcut key (1-10) to assign, or null.
34+
* @returns {HTMLButtonElement} - The newly created button element.
35+
*/
36+
createCrossChatButton: function (type, shortcutKey) {
37+
const buttonElement = document.createElement('button');
38+
buttonElement.type = 'button';
39+
40+
const icons = { copy: '📋', paste: '📥' };
41+
const baseTooltips = { copy: 'Copy prompt from input area', paste: 'Paste stored prompt' };
42+
43+
buttonElement.innerHTML = icons[type];
44+
45+
// Style the button
46+
buttonElement.style.cssText = `
47+
background-color: transparent;
48+
border: none;
49+
cursor: pointer;
50+
padding: 1px;
51+
font-size: 20px;
52+
margin-right: 5px;
53+
margin-bottom: 5px;
54+
`;
55+
56+
// --- Tooltip & Shortcut ---
57+
let shortcutDescription = '';
58+
if (shortcutKey) {
59+
buttonElement.dataset.shortcutKey = shortcutKey.toString();
60+
const displayKey = shortcutKey === 10 ? 0 : shortcutKey;
61+
shortcutDescription = ` (Shortcut: Alt+${displayKey})`;
62+
}
63+
64+
const updateTooltip = (text) => {
65+
buttonElement.setAttribute('title', text + shortcutDescription);
66+
};
67+
68+
updateTooltip(baseTooltips[type]);
69+
70+
// --- Event Listeners ---
71+
buttonElement.addEventListener('click', (event) => {
72+
event.preventDefault();
73+
if (type === 'copy') {
74+
const editor = window.InjectionTargetsOnWebsite.selectors.editors
75+
.map(s => document.querySelector(s))
76+
.find(el => el);
77+
78+
if (!editor) {
79+
logConCgp('[buttons-cross-chat] Editor area not found for copy.');
80+
return;
81+
}
82+
const text = editor.value || editor.innerText || '';
83+
84+
chrome.runtime.sendMessage({ type: 'saveStoredPrompt', promptText: text }, () => {
85+
logConCgp('[buttons-cross-chat] Prompt saved.');
86+
// Visually indicate success by briefly changing the tooltip
87+
const originalTitle = buttonElement.getAttribute('title');
88+
updateTooltip('Copied!');
89+
setTimeout(() => updateTooltip(baseTooltips.copy), 1500);
90+
});
91+
92+
if (window.globalCrossChatConfig?.autosendCopy) {
93+
processCustomSendButtonClick(event, text, true);
94+
}
95+
} else if (type === 'paste') {
96+
chrome.runtime.sendMessage({ type: 'getStoredPrompt' }, (response) => {
97+
if (response?.promptText) {
98+
// Use shift key to override autosend setting, consistent with other buttons
99+
const autoSend = event.shiftKey ? !window.globalCrossChatConfig.autosendPaste : window.globalCrossChatConfig.autosendPaste;
100+
processCustomSendButtonClick(event, response.promptText, autoSend);
101+
} else {
102+
logConCgp('[buttons-cross-chat] No prompt to paste.');
103+
const originalTitle = buttonElement.getAttribute('title');
104+
updateTooltip('*No prompt has been saved*');
105+
setTimeout(() => updateTooltip(baseTooltips.paste), 2000);
106+
}
107+
});
108+
}
109+
});
110+
111+
if (type === 'paste') {
112+
let tooltipFetchTimeout;
113+
buttonElement.addEventListener('mouseover', () => {
114+
clearTimeout(tooltipFetchTimeout);
115+
tooltipFetchTimeout = setTimeout(() => {
116+
chrome.runtime.sendMessage({ type: 'getStoredPrompt' }, (response) => {
117+
const promptText = response?.promptText;
118+
if (promptText) {
119+
const truncatedPrompt = promptText.length > 200 ? promptText.substring(0, 197) + '...' : promptText;
120+
updateTooltip(truncatedPrompt);
121+
} else {
122+
updateTooltip('*No prompt has been saved*');
123+
}
124+
});
125+
}, 300); // 300ms debounce
126+
});
127+
128+
buttonElement.addEventListener('mouseout', () => {
129+
clearTimeout(tooltipFetchTimeout);
130+
updateTooltip(baseTooltips.paste); // Reset to default tooltip
131+
});
132+
}
133+
134+
return buttonElement;
135+
},
30136
/**
31137
* Creates a custom send button based on the provided configuration.
32138
* @param {Object} buttonConfig - The configuration object for the custom button.
33139
* @param {number} buttonIndex - The index of the button in the custom buttons array.
34140
* @param {Function} onClickHandler - The function to handle the button's click event.
141+
* @param {number|null} [overrideShortcutKey=null] - An optional shortcut key to override the default calculation.
35142
* @returns {HTMLButtonElement} - The newly created custom send button element.
36143
*/
37-
createCustomSendButton: function (buttonConfig, buttonIndex, onClickHandler) {
144+
createCustomSendButton: function (buttonConfig, buttonIndex, onClickHandler, overrideShortcutKey = null) {
38145
const customButtonElement = document.createElement('button');
39146
customButtonElement.type = 'button'; // Prevent form being defaut type, that is "submit".
40147
customButtonElement.innerHTML = buttonConfig.icon;
41148
customButtonElement.setAttribute('data-testid', `custom-send-button-${buttonIndex}`);
42149

43150
// Assign keyboard shortcuts to the first 10 non-separator buttons if shortcuts are enabled
44-
let assignedShortcutKey = null;
45-
if (globalMaxExtensionConfig.enableShortcuts) {
46-
assignedShortcutKey = this.determineShortcutKeyForButtonIndex(buttonIndex);
47-
if (assignedShortcutKey !== null) {
48-
customButtonElement.dataset.shortcutKey = assignedShortcutKey.toString();
49-
}
151+
let assignedShortcutKey = overrideShortcutKey;
152+
if (assignedShortcutKey === null && globalMaxExtensionConfig.enableShortcuts) {
153+
assignedShortcutKey = this.determineShortcutKeyForButtonIndex(buttonIndex, 0); // Pass 0 as offset for old logic
154+
}
155+
156+
if (assignedShortcutKey !== null) {
157+
customButtonElement.dataset.shortcutKey = assignedShortcutKey.toString();
50158
}
51159

52160
// Prepare tooltip parts: append (Auto-sends) if autoSend behavior is enabled
53161
const autoSendDescription = buttonConfig.autoSend ? ' (Auto-sends)' : '';
54-
const shortcutDescription = assignedShortcutKey !== null ? ` (Shortcut: Alt+${assignedShortcutKey})` : '';
162+
const shortcutDescription = assignedShortcutKey !== null ? ` (Shortcut: Alt+${assignedShortcutKey === 10 ? 0 : assignedShortcutKey})` : '';
55163

56164
// Set the tooltip (title attribute) combining the button text with auto-send and shortcut info
57165
customButtonElement.setAttribute('title', `${buttonConfig.text}${autoSendDescription}${shortcutDescription}`);
@@ -74,16 +182,20 @@ window.MaxExtensionButtons = {
74182

75183
/**
76184
* Determines the appropriate shortcut key for a button based on its index, skipping separator buttons.
185+
* @param {number} offset - A number to offset the calculated shortcut index.
77186
* @param {number} buttonIndex - The index of the button in the custom buttons array.
78187
* @returns {number|null} - The assigned shortcut key (1-10) or null if no shortcut is assigned.
79188
*/
80-
determineShortcutKeyForButtonIndex: function (buttonIndex) {
189+
determineShortcutKeyForButtonIndex: function (buttonIndex, offset = 0) {
81190
let shortcutAssignmentCount = 0;
82191
for (let i = 0; i < globalMaxExtensionConfig.customButtons.length; i++) {
83192
if (!globalMaxExtensionConfig.customButtons[i].separator) {
84193
shortcutAssignmentCount++;
85-
if (i === buttonIndex && shortcutAssignmentCount <= 10) {
86-
return shortcutAssignmentCount % 10; // 0 represents 10
194+
if (i === buttonIndex) {
195+
const finalShortcutIndex = offset + shortcutAssignmentCount;
196+
if (finalShortcutIndex <= 10) {
197+
return finalShortcutIndex;
198+
}
87199
}
88200
}
89201
}

code-notes.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ This "Crash-only" principle informs the design of `init.js`, `buttons-injection.
3535
- Provides unified storage for all extension settings
3636
- Implements reset functionality for various setting types
3737
- **Message Handlers:**
38+
- **Cross-Chat Prompt Sharing Module Handlers:**
39+
- `getCrossChatModuleSettings`: Retrieves global settings for the module (enabled, autosend, etc.).
40+
- `saveCrossChatModuleSettings`: Saves changes to the module's global settings.
41+
- `saveStoredPrompt`: Stores the prompt text copied from a chat input.
42+
- `getStoredPrompt`: Retrieves the currently stored prompt text.
43+
- `clearStoredPrompt`: Clears the stored prompt text.
44+
- **Profile Management Handlers:**
45+
- Processes messages for loading, saving, switching, and deleting user profiles.
46+
- Manages creation of the default profile.
3847
- Processes messages from content scripts and popup pages
3948
- Handles floating panel settings with message types:
4049
- 'getFloatingPanelSettings': Retrieves settings for a specific website
@@ -145,8 +154,8 @@ This "Crash-only" principle informs the design of `init.js`, `buttons-injection.
145154

146155
### `init.js`
147156

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.
157+
- **Purpose:** Main initialization script for the content script. It acts as the **Director** of the UI setup and embodies the **Crash-only** philosophy.
158+
- **Role:** Implements a **"decide first, then create"** architecture. It asynchronously loads all necessary configurations—both the user's current profile and global module settings (like Cross-Chat Prompt Sharing)—*before* rendering any UI. It then checks if the floating panel should be visible. Based on this setting, it either injects buttons into the traditional inline location (via `buttons-injection.js`) or directly into the floating panel. Its main initialization function (`publicStaticVoidMain`) is **idempotent** and is called on page load, SPA navigation, and panel closing to ensure a consistent and reliable UI state.
150159

151160
### `interface.js`
152161

@@ -156,12 +165,15 @@ This "Crash-only" principle informs the design of `init.js`, `buttons-injection.
156165
### `buttons.js`
157166

158167
- **Purpose:** Manages the creation and functionality of custom send buttons.
159-
- **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.
168+
- **Role:** Contains the logic for creating all button types.
169+
- `createCustomSendButton()`: Creates standard prompt buttons from the user's profile configuration. It has been updated to accept an overridden shortcut key to support unified assignment.
170+
- `createCrossChatButton()`: Creates the new "Copy Prompt" and "Paste & Send Prompt" buttons for the cross-chat module. This function includes the logic for saving/retrieving prompts from the service worker and handling the dynamic tooltip on hover.
171+
- `processCustomSendButtonClick()`: The central click handler that is called by all buttons to insert text and optionally trigger a send action. It decides which site-specific function to call and contains the logic to divert clicks to the prompt queue when Queue Mode is active.
160172

161173
### `buttons-init.js`
162174

163-
- **Purpose:** Acts as a **UI Factory**. It contains the logic to generate a complete set of buttons and toggles.
164-
- **Role:** It is called by the "Director" (`init.js`) to populate a specified container, which can be either the inline injection point or the floating panel's content area. It is no longer responsible for making decisions about panel visibility.
175+
- **Purpose:** Acts as a **UI Factory & Orchestrator**. It contains the logic to generate a complete and ordered set of all buttons and toggles.
176+
- **Role:** Called by the "Director" (`init.js`), its `generateAndAppendAllButtons()` function is the single source of truth for UI generation. It builds a master list of all buttons to be rendered (cross-chat module buttons and standard custom buttons), places them in the correct order based on user settings (e.g., "before" or "after" for cross-chat buttons), and assigns sequential keyboard shortcuts (`Alt+1`, `Alt+2`, etc.) to the entire set. It populates a specified container, which can be either the inline injection point or the floating panel's content area.
165177

166178
### `buttons-injection.js`
167179

0 commit comments

Comments
 (0)