@@ -52,65 +52,87 @@ 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- 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- }
55+ generateAndAppendAllButtons : async function ( container , isPanel ) {
56+ // --- Create a unified list of all buttons to be rendered ---
57+ const allButtonDefs = [ ] ;
58+ let nonSeparatorCount = 0 ;
6559
66- // 2. Add standard custom buttons
67- globalMaxExtensionConfig . customButtons . forEach ( config => {
68- allButtonDefs . push ( { type : 'custom' , config : config } ) ;
69- } ) ;
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+ }
7065
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- }
66+ // 2. Add standard custom buttons
67+ globalMaxExtensionConfig . customButtons . forEach ( config => {
68+ allButtonDefs . push ( { type : 'custom' , config : config } ) ;
69+ } ) ;
7670
77- // --- Render all buttons from the unified list ---
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+ }
7876
79- // Add floating panel toggle first, if applicable
80- if ( window . MaxExtensionFloatingPanel && ! isPanel ) {
81- const floatingPanelToggleButton = window . MaxExtensionFloatingPanel . createPanelToggleButton ( ) ;
82- container . appendChild ( floatingPanelToggleButton ) ;
83- logConCgp ( '[init] Floating panel toggle button has been created and appended for inline container.' ) ;
84- }
77+ // --- Render all buttons from the unified list ---
8578
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 ) {
90- const separatorElement = MaxExtensionUtils . createSeparator ( ) ;
91- container . appendChild ( separatorElement ) ;
92- logConCgp ( '[init] Separator element has been created and appended.' ) ;
93- return ; // Skip to next item
94- }
79+ // Add floating panel toggle first, if applicable
80+ if ( window . MaxExtensionFloatingPanel && ! isPanel ) {
81+ const floatingPanelToggleButton = window . MaxExtensionFloatingPanel . createPanelToggleButton ( ) ;
82+ container . appendChild ( floatingPanelToggleButton ) ;
83+ logConCgp ( '[init] Floating panel toggle button has been created and appended for inline container.' ) ;
84+ }
9585
96- // Assign a shortcut key if enabled and available
97- let shortcutKey = null ;
98- if ( globalMaxExtensionConfig . enableShortcuts && nonSeparatorCount < 10 ) {
99- shortcutKey = nonSeparatorCount + 1 ;
100- }
86+ // Inline Profile Selector BEFORE buttons
87+ if ( window . globalInlineSelectorConfig ?. enabled && window . globalInlineSelectorConfig . placement === 'before' && ! isPanel ) {
88+ if ( typeof this . createInlineProfileSelector === 'function' ) {
89+ const selectorElBefore = await this . createInlineProfileSelector ( ) ;
90+ if ( selectorElBefore ) {
91+ container . appendChild ( selectorElBefore ) ;
92+ logConCgp ( '[init] Inline Profile Selector appended before buttons.' ) ;
93+ }
94+ }
95+ }
10196
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- }
97+ // Process the unified list to create and append buttons
98+ allButtonDefs . forEach ( ( def , index ) => {
99+ // Handle separators from custom buttons
100+ if ( def . type === 'custom' && def . config . separator ) {
101+ const separatorElement = MaxExtensionUtils . createSeparator ( ) ;
102+ container . appendChild ( separatorElement ) ;
103+ logConCgp ( '[init] Separator element has been created and appended.' ) ;
104+ return ; // Skip to next item
105+ }
108106
109- container . appendChild ( buttonElement ) ;
110- nonSeparatorCount ++ ;
111- logConCgp ( `[init] Button ${ nonSeparatorCount } (${ def . type } ) has been created and appended.` ) ;
112- } ) ;
113- } ,
107+ // Assign a shortcut key if enabled and available
108+ let shortcutKey = null ;
109+ if ( globalMaxExtensionConfig . enableShortcuts && nonSeparatorCount < 10 ) {
110+ shortcutKey = nonSeparatorCount + 1 ;
111+ }
112+
113+ let buttonElement ;
114+ if ( def . type === 'copy' || def . type === 'paste' ) {
115+ buttonElement = MaxExtensionButtons . createCrossChatButton ( def . type , shortcutKey ) ;
116+ } else { // 'custom'
117+ buttonElement = MaxExtensionButtons . createCustomSendButton ( def . config , index , processCustomSendButtonClick , shortcutKey ) ;
118+ }
119+
120+ container . appendChild ( buttonElement ) ;
121+ nonSeparatorCount ++ ;
122+ logConCgp ( `[init] Button ${ nonSeparatorCount } (${ def . type } ) has been created and appended.` ) ;
123+ } ) ;
124+
125+ // Inline Profile Selector AFTER buttons
126+ if ( window . globalInlineSelectorConfig ?. enabled && window . globalInlineSelectorConfig . placement === 'after' && ! isPanel ) {
127+ if ( typeof this . createInlineProfileSelector === 'function' ) {
128+ const selectorElAfter = await this . createInlineProfileSelector ( ) ;
129+ if ( selectorElAfter ) {
130+ container . appendChild ( selectorElAfter ) ;
131+ logConCgp ( '[init] Inline Profile Selector appended after buttons.' ) ;
132+ }
133+ }
134+ }
135+ } ,
114136
115137 /**
116138 * Creates and inserts custom buttons and toggles into the target container element.
@@ -182,20 +204,75 @@ window.MaxExtensionButtonsInit = {
182204 }
183205} ;
184206
185- // Listen for profile change events
186- chrome . runtime . onMessage . addListener ( ( message , sender , sendResponse ) => {
187- if ( message . type === 'profileChanged' ) {
188- logConCgp ( '[init] Received profile change notification' ) ;
207+ // --- Helper to create Inline Profile Selector element ---
208+ /**
209+ * Creates and returns a DOM element for the inline profile selector.
210+ * @returns {Promise<HTMLElement|null> }
211+ */
212+ window . MaxExtensionButtonsInit . createInlineProfileSelector = async function ( ) {
213+ try {
214+ const container = document . createElement ( 'div' ) ;
215+ container . style . display = 'flex' ;
216+ container . style . alignItems = 'center' ;
217+ container . style . gap = '4px' ;
218+ container . style . marginRight = '8px' ;
219+
220+ const label = document . createElement ( 'span' ) ;
221+ label . textContent = 'Profile:' ;
222+ label . style . fontSize = '12px' ;
223+ label . style . color = '#888' ;
189224
190- // Update the global config with the new profile data
191- window . globalMaxExtensionConfig = message . config ;
192- // Note: Cross-chat config is global and does not change with profile.
225+ const select = document . createElement ( 'select' ) ;
226+ select . title = 'Switch active profile' ;
227+ select . style . padding = '2px' ;
228+ select . style . zIndex = '100000' ;
229+ select . tabIndex = 0 ;
193230
194- // Update the UI components
195- window . MaxExtensionButtonsInit . updateButtonsForProfileChange ( ) ;
231+ // Prevent hostile site handlers from closing the dropdown immediately on SPA UIs (e.g. ChatGPT)
232+ const stop = ( e ) => { e . stopPropagation ( ) ; } ;
233+ [ 'pointerdown' , 'mousedown' , 'mouseup' , 'click' , 'touchstart' , 'touchend' , 'keydown' ] . forEach ( evt => {
234+ select . addEventListener ( evt , stop , { capture : true } ) ;
235+ } ) ;
236+
237+ // Load profiles and current profile
238+ const profilesResponse = await chrome . runtime . sendMessage ( { type : 'listProfiles' } ) ;
239+ const { currentProfile } = await chrome . storage . local . get ( 'currentProfile' ) ;
196240
197- // Acknowledge the message
198- sendResponse ( { success : true } ) ;
241+ const profileNames = Array . isArray ( profilesResponse ?. profiles ) ? profilesResponse . profiles : [ ] ;
242+ profileNames . forEach ( ( name ) => {
243+ const opt = document . createElement ( 'option' ) ;
244+ opt . value = name ;
245+ opt . textContent = name ;
246+ if ( name === currentProfile ) opt . selected = true ;
247+ select . appendChild ( opt ) ;
248+ } ) ;
249+
250+ select . addEventListener ( 'change' , ( e ) => {
251+ const selected = e . target . value ;
252+ // Request switch and refresh immediately using service worker response for reliability.
253+ chrome . runtime . sendMessage ( { type : 'switchProfile' , profileName : selected } , ( response ) => {
254+ if ( response && response . config ) {
255+ // Immediate local refresh; SW also broadcasts to other tabs
256+ if ( typeof window . __OCP_partialRefreshUI === 'function' ) {
257+ window . __OCP_partialRefreshUI ( response . config ) ;
258+ } else if ( typeof window . __OCP_nukeAndRefresh === 'function' ) {
259+ window . __OCP_nukeAndRefresh ( response . config ) ;
260+ } else if ( window . MaxExtensionButtonsInit && typeof window . MaxExtensionButtonsInit . updateButtonsForProfileChange === 'function' ) {
261+ // Fallback: partial refresh
262+ window . globalMaxExtensionConfig = response . config ;
263+ window . MaxExtensionButtonsInit . updateButtonsForProfileChange ( ) ;
264+ }
265+ }
266+ } ) ;
267+ } ) ;
268+
269+ container . appendChild ( label ) ;
270+ container . appendChild ( select ) ;
271+ return container ;
272+ } catch ( err ) {
273+ logConCgp ( '[init] Error creating inline profile selector:' , err ?. message || err ) ;
274+ return null ;
199275 }
200- return true ;
201- } ) ;
276+ } ;
277+
278+ // Profile change messaging is handled centrally in init.js to avoid duplicate listeners.
0 commit comments