Skip to content

Commit 351bef2

Browse files
committed
Refactor collapsible functionality: centralize logic in popup-page-collapsible.js, improve UI handling for modules and advanced sections, and enhance element visibility management.
1 parent 4da30e1 commit 351bef2

File tree

5 files changed

+127
-88
lines changed

5 files changed

+127
-88
lines changed

modules/popup-page-modules-promptShare.js

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
document.addEventListener('DOMContentLoaded', () => {
44
// --- Module Elements ---
5-
const modulesSection = document.getElementById('modulesSection');
65
const crossChatModule = document.getElementById('crossChatModule');
76
const enableToggle = document.getElementById('crossChatModuleEnableToggle');
87
const settingsContainer = document.getElementById('crossChatModuleSettings');
@@ -17,34 +16,17 @@ document.addEventListener('DOMContentLoaded', () => {
1716
const refreshPromptBtn = document.getElementById('refreshStoredPrompt');
1817
const clearPromptBtn = document.getElementById('clearStoredPrompt');
1918

20-
if (!modulesSection || !crossChatModule || !enableToggle) {
21-
console.log("Cross-Chat module elements not found, skipping initialization.");
19+
// Gracefully exit if essential elements for this module are missing.
20+
// This no longer breaks the parent container's collapsible functionality.
21+
if (!crossChatModule || !enableToggle || !settingsContainer || !autosendCopyToggle) {
22+
console.warn("Cross-Chat module elements not found, skipping feature initialization.");
23+
// If the module container exists, we hide it to prevent showing a broken UI.
24+
if (crossChatModule) {
25+
crossChatModule.style.display = 'none';
26+
}
2227
return;
2328
}
2429

25-
// --- Collapsible Logic ---
26-
function setupCollapsible(section) {
27-
const header = section.querySelector('.section-header, .subsection-header');
28-
const toggleIcon = header.querySelector('.toggle-icon');
29-
header.addEventListener('click', () => {
30-
const isExpanded = section.classList.toggle('expanded');
31-
if (toggleIcon) {
32-
toggleIcon.style.transform = isExpanded ? 'rotate(90deg)' : 'rotate(0deg)';
33-
}
34-
35-
// Update settings visibility when crossChatModule is expanded/collapsed
36-
if (section === crossChatModule) {
37-
updateSettingsVisibility();
38-
}
39-
});
40-
}
41-
42-
setupCollapsible(modulesSection);
43-
setupCollapsible(crossChatModule);
44-
45-
// Ensure crossChatModule starts collapsed
46-
crossChatModule.classList.remove('expanded');
47-
4830
// --- State Management ---
4931
const defaultSettings = {
5032
enabled: false,
@@ -93,7 +75,6 @@ document.addEventListener('DOMContentLoaded', () => {
9375
function updateUIFromState() {
9476
// Main toggle
9577
enableToggle.checked = currentSettings.enabled;
96-
updateSettingsVisibility();
9778

9879
// Settings
9980
autosendCopyToggle.checked = currentSettings.autosendCopy;
@@ -105,17 +86,31 @@ document.addEventListener('DOMContentLoaded', () => {
10586
break;
10687
}
10788
}
89+
// Update visibility based on the new state
90+
updateSettingsVisibility();
10891
}
10992

11093
// Helper function to manage settings visibility based on both toggle state and collapsible state
11194
function updateSettingsVisibility() {
11295
const isModuleExpanded = crossChatModule.classList.contains('expanded');
11396
const isToggleEnabled = currentSettings.enabled;
114-
97+
11598
// Only show settings if both the module is expanded AND the toggle is enabled
11699
settingsContainer.style.display = (isModuleExpanded && isToggleEnabled) ? 'block' : 'none';
117100
}
118101

102+
// --- Observer for Section Expansion ---
103+
// The centralized collapsible script handles the click and toggles the .expanded class.
104+
// We use a MutationObserver to react to that class change and update our UI accordingly.
105+
const observer = new MutationObserver(() => {
106+
// When the section is expanded or collapsed, we need to re-evaluate
107+
// whether the settings sub-section should be visible.
108+
updateSettingsVisibility();
109+
});
110+
111+
// Start observing the crossChatModule for class attribute changes.
112+
observer.observe(crossChatModule, { attributes: true, attributeFilter: ['class'] });
113+
119114
// --- Prompt Actions ---
120115
async function refreshStoredPrompt() {
121116
try {

popup-page-scripts/popup-page-advanced.js

Lines changed: 38 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,39 @@
44
*/
55

66
document.addEventListener('DOMContentLoaded', () => {
7-
// Set up collapsible section functionality
87
const advancedSection = document.getElementById('advancedSection');
9-
const toggleIcon = advancedSection.querySelector('.toggle-icon');
8+
const advancedHelpSection = document.getElementById('advancedHelpSection');
109

11-
function toggleSection(section, icon) {
12-
section.classList.toggle('expanded');
13-
icon.style.transform = section.classList.contains('expanded')
14-
? 'rotate(90deg)'
15-
: 'rotate(0deg)';
16-
}
10+
// This script only needs to handle logic specific to the advanced settings.
11+
// The general collapsible behavior is now managed by popup-page-collapsible.js.
1712

18-
// Toggle section visibility
19-
advancedSection.querySelector('.section-header').addEventListener('click', () => {
20-
toggleSection(advancedSection, toggleIcon);
21-
22-
// If advanced section is collapsed, ensure help section is also collapsed
23-
if (!advancedSection.classList.contains('expanded')) {
24-
advancedHelpSection.classList.remove('expanded');
25-
helpToggleIcon.style.transform = 'rotate(0deg)';
26-
}
27-
});
13+
// --- Collapsible Dependency Logic ---
14+
// We have a special case: if the main "Advanced" section is collapsed, the
15+
// inner "Help" section should also be forced into a collapsed state.
16+
// A MutationObserver handles this dependency cleanly.
17+
if (advancedSection && advancedHelpSection) {
18+
const helpToggleIcon = advancedHelpSection.querySelector('.toggle-icon');
2819

29-
// Initialize in collapsed state
30-
advancedSection.classList.remove('expanded');
31-
toggleIcon.style.transform = 'rotate(0deg)';
32-
33-
// Set up help section collapsible functionality
34-
const advancedHelpSection = document.getElementById('advancedHelpSection');
35-
const helpToggleIcon = advancedHelpSection.querySelector('.toggle-icon');
20+
const parentObserver = new MutationObserver(() => {
21+
// Check if the parent is collapsed while the child is expanded.
22+
if (!advancedSection.classList.contains('expanded') && advancedHelpSection.classList.contains('expanded')) {
23+
// Force the child to collapse.
24+
advancedHelpSection.classList.remove('expanded');
25+
26+
// Manually reset the icon rotation, as this change is programmatic
27+
// and won't trigger the click listener in the central script.
28+
if (helpToggleIcon) {
29+
helpToggleIcon.style.transform = 'rotate(0deg)';
30+
}
31+
}
32+
});
3633

37-
// Toggle help section visibility
38-
advancedHelpSection.querySelector('.section-header').addEventListener('click', (event) => {
39-
// Prevent the click from bubbling up to the parent advanced section
40-
event.stopPropagation();
41-
toggleSection(advancedHelpSection, helpToggleIcon);
42-
});
34+
parentObserver.observe(advancedSection, {
35+
attributes: true,
36+
attributeFilter: ['class']
37+
});
38+
}
4339

44-
// Initialize help section in collapsed state
45-
advancedHelpSection.classList.remove('expanded');
46-
helpToggleIcon.style.transform = 'rotate(0deg)';
4740

4841
const websiteSelect = document.getElementById('selectorWebsiteSelect');
4942
const selectorConfig = document.getElementById('selectorConfig');
@@ -61,7 +54,7 @@ document.addEventListener('DOMContentLoaded', () => {
6154
site: site
6255
}, resolve);
6356
});
64-
57+
6558
if (response && response.selectors) {
6659
// Use custom selectors if available
6760
selectorConfig.value = JSON.stringify(response.selectors, null, 2);
@@ -100,12 +93,12 @@ document.addEventListener('DOMContentLoaded', () => {
10093
try {
10194
// Parse the JSON to validate it
10295
const config = JSON.parse(selectorConfig.value);
103-
96+
10497
// Validate the structure
10598
if (!validateSelectors(config)) {
10699
throw new Error('Invalid selector structure');
107100
}
108-
101+
109102
// Save to storage
110103
const response = await new Promise(resolve => {
111104
chrome.runtime.sendMessage({
@@ -114,7 +107,7 @@ document.addEventListener('DOMContentLoaded', () => {
114107
selectors: config
115108
}, resolve);
116109
});
117-
110+
118111
if (response && response.success) {
119112
showToast('Selectors saved successfully', 'success');
120113
} else {
@@ -137,7 +130,7 @@ document.addEventListener('DOMContentLoaded', () => {
137130
selectors: null // null means remove
138131
}, resolve);
139132
});
140-
133+
141134
if (response && response.success) {
142135
// Load the defaults
143136
const defaultSelectors = getDefaultSelectorsForSite(websiteSelect.value);
@@ -154,11 +147,11 @@ document.addEventListener('DOMContentLoaded', () => {
154147

155148
// Validate the selector structure
156149
function validateSelectors(selectors) {
157-
return selectors &&
158-
selectors.containers && Array.isArray(selectors.containers) &&
159-
selectors.sendButtons && Array.isArray(selectors.sendButtons) &&
160-
selectors.editors && Array.isArray(selectors.editors) &&
161-
typeof selectors.buttonsContainerId === 'string';
150+
return selectors &&
151+
selectors.containers && Array.isArray(selectors.containers) &&
152+
selectors.sendButtons && Array.isArray(selectors.sendButtons) &&
153+
selectors.editors && Array.isArray(selectors.editors) &&
154+
typeof selectors.buttonsContainerId === 'string';
162155
}
163156

164157
// Event listeners
@@ -168,4 +161,4 @@ document.addEventListener('DOMContentLoaded', () => {
168161

169162
// Initial load
170163
loadSelectors();
171-
});
164+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @file
3+
* Centralized logic for handling all collapsible sections in the popup.
4+
*
5+
* This script's sole responsibility is to make UI sections expandable and
6+
* collapsible. It scans the DOM for any element with the `.collapsible` class
7+
* and attaches the necessary event listeners.
8+
*
9+
* This decouples the UI behavior from the feature-specific logic in other
10+
* scripts, making the application more robust and easier to maintain.
11+
* For example, if a module's content fails to load, the section will still
12+
* remain collapsible.
13+
*/
14+
15+
'use strict';
16+
17+
document.addEventListener('DOMContentLoaded', () => {
18+
/**
19+
* Finds all elements with the `.collapsible` class and makes them interactive.
20+
*/
21+
function initializeCollapsibleSections() {
22+
// Select all top-level collapsible containers.
23+
const collapsibleSections = document.querySelectorAll('.collapsible');
24+
25+
collapsibleSections.forEach(section => {
26+
// Find the header element within the section. It can be a .section-header or .subsection-header.
27+
const header = section.querySelector('.section-header, .subsection-header');
28+
29+
// Find the icon used to indicate the expanded/collapsed state.
30+
const toggleIcon = header ? header.querySelector('.toggle-icon') : null;
31+
32+
// If a header is found, attach the click event listener.
33+
if (header) {
34+
header.addEventListener('click', () => {
35+
// Toggle the 'expanded' class on the main section container.
36+
const isExpanded = section.classList.toggle('expanded');
37+
38+
// Rotate the toggle icon if it exists.
39+
if (toggleIcon) {
40+
toggleIcon.style.transform = isExpanded ? 'rotate(90deg)' : 'rotate(0deg)';
41+
}
42+
});
43+
}
44+
});
45+
}
46+
47+
// Run the initialization function once the DOM is fully loaded.
48+
initializeCollapsibleSections();
49+
});

popup-page-scripts/popup-page-floating-window-handler.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,20 +111,19 @@ document.addEventListener('DOMContentLoaded', () => {
111111
return;
112112
}
113113

114-
// Set up collapsible functionality
115-
const header = floatingWindowSection.querySelector('.section-header');
116-
const toggleIcon = floatingWindowSection.querySelector('.toggle-icon');
117-
118-
header.addEventListener('click', () => {
119-
const isExpanded = floatingWindowSection.classList.toggle('expanded');
120-
toggleIcon.style.transform = isExpanded ? 'rotate(90deg)' : 'rotate(0deg)';
121-
122-
// Populate the list only when the section is expanded
123-
if (isExpanded) {
114+
// The centralized collapsible script handles toggling the .expanded class.
115+
// We observe for that class change to populate the list only when needed, which is more efficient.
116+
const observer = new MutationObserver(() => {
117+
if (floatingWindowSection.classList.contains('expanded')) {
124118
populateFloatingWindowSitesList();
125119
}
126120
});
127121

122+
observer.observe(floatingWindowSection, {
123+
attributes: true,
124+
attributeFilter: ['class']
125+
});
126+
128127
// Event delegation for individual reset buttons
129128
sitesListDiv.addEventListener('click', (e) => {
130129
if (e.target.matches('button.small.danger')) {

popup.html

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ <h2>Button Configuration</h2>
173173
</section>
174174

175175
<!-- ===== Modules Section ===== -->
176+
<!-- This is a top-level collapsible container. The .collapsible class enables the expand/collapse behavior via popup-page-collapsible.js -->
176177
<section class="section collapsible" id="modulesSection">
177178
<div class="section-header">
178179
<h2>
@@ -183,6 +184,8 @@ <h2>
183184
</div>
184185
<div class="section-content">
185186
<!-- Cross-Chat Prompt Sharing Module -->
187+
<!-- This is a nested collapsible module. It has its own .collapsible class and header. -->
188+
<!-- Its functionality is managed by popup-page-modules-promptShare.js, but its collapsible behavior is now managed centrally by popup-page-collapsible.js -->
186189
<div class="collapsible" id="crossChatModule">
187190
<div class="section-header subsection-header">
188191
<h3>
@@ -197,7 +200,7 @@ <h3>
197200
<input
198201
type="checkbox"
199202
id="crossChatModuleEnableToggle"
200-
title="Enable this module to add two new buttons (📋 Copy and 📥 Paste) to AI chat pages. This allows you to easily transfer prompts between different AI platforms."
203+
title="Enables the Cross-Chat module, which adds 'Copy' and 'Paste & Send' buttons to AI chat pages. This allows you to easily transfer prompts between different AI platforms."
201204
aria-label="Enable Cross-Chat Prompt Sharing"
202205
/>
203206
<span class="slider round"></span>
@@ -762,6 +765,8 @@ <h2>Theme</h2>
762765
<script src="log.js"></script>
763766
<script src="utils.js"></script>
764767
<script src="/popup-page-scripts/popup-page-visuals.js"></script>
768+
<!-- NEW SCRIPT: Centralized handler for all collapsible sections -->
769+
<script src="/popup-page-scripts/popup-page-collapsible.js"></script>
765770
<script src="/popup-page-scripts/popup-page-profiles.js"></script>
766771
<script src="/popup-page-scripts/popup-page-backup-handler.js"></script>
767772
<script src="/popup-page-scripts/popup-page-customButtons.js"></script>
@@ -772,9 +777,7 @@ <h2>Theme</h2>
772777
<script src="/popup-page-scripts/popup-page-advanced.js"></script>
773778
<!-- Floating Window Settings Handler script -->
774779
<script src="/popup-page-scripts/popup-page-floating-window-handler.js"></script>
775-
<!-- NEW SCRIPT: Cross-Chat Module UI handler script -->
780+
<!-- Cross-Chat Module UI handler script -->
776781
<script src="/modules/popup-page-modules-promptShare.js"></script>
777782
</body>
778-
</html>
779-
780-
783+
</html>

0 commit comments

Comments
 (0)