1
1
// feedback.js
2
- document . addEventListener ( "DOMContentLoaded" , ( ) => {
2
+
3
+ function feedbackModalInit ( ) {
4
+ // Remove any previous event listeners by replacing the button and modal with clones
5
+ const oldBtn = document . querySelector ( "#feedbackButton" ) ;
6
+ const oldModal = document . querySelector ( "#feedbackModal" ) ;
7
+ if ( oldBtn ) {
8
+ const newBtn = oldBtn . cloneNode ( true ) ;
9
+ oldBtn . parentNode . replaceChild ( newBtn , oldBtn ) ;
10
+ }
11
+ if ( oldModal ) {
12
+ const newModal = oldModal . cloneNode ( true ) ;
13
+ oldModal . parentNode . replaceChild ( newModal , oldModal ) ;
14
+ }
15
+
16
+ // Now re-select after replacement
3
17
const feedbackButton = document . querySelector ( "#feedbackButton" ) ;
4
18
const modal = document . querySelector ( "#feedbackModal" ) ;
5
19
@@ -30,11 +44,9 @@ document.addEventListener("DOMContentLoaded", () => {
30
44
}
31
45
32
46
function openModal ( ) {
33
- // store previous active element so we can restore focus when modal closes
34
47
lastActiveElement = document . activeElement ;
35
48
modal . classList . remove ( "tw-hidden" ) ;
36
49
calculatePosition ( ) ;
37
- // focus the textarea for immediate typing if present
38
50
const ta = modal . querySelector ( "textarea" ) ;
39
51
if ( ta ) ta . focus ( ) ;
40
52
}
@@ -44,35 +56,25 @@ document.addEventListener("DOMContentLoaded", () => {
44
56
form . reset ( ) ;
45
57
errorView . classList . add ( "tw-hidden" ) ;
46
58
successView . classList . add ( "tw-hidden" ) ;
47
- // remove layout class when hidden to avoid display conflicts
48
59
successView . classList . remove ( "tw-flex" ) ;
49
- // clear any inline positioning set during calculatePosition
50
60
try {
51
61
modal . style . top = "" ;
52
62
modal . style . bottom = "" ;
53
- } catch ( e ) {
54
- /* ignore */
55
- }
63
+ } catch ( e ) { }
56
64
formView . classList . remove ( "tw-hidden" ) ;
57
- // restore focus to previously active element
58
65
try {
59
66
if ( lastActiveElement && typeof lastActiveElement . focus === "function" ) {
60
67
lastActiveElement . focus ( ) ;
61
68
}
62
- } catch ( e ) {
63
- /* ignore */
64
- }
69
+ } catch ( e ) { }
65
70
}
66
71
67
72
function calculatePosition ( ) {
68
- // class-based positioning like the Vue component: toggle top-full / bottom-full
69
73
try {
70
74
const btnRect = feedbackButton . getBoundingClientRect ( ) ;
71
75
const screenHeight = window . innerHeight ;
72
76
const buttonCenter = btnRect . top + btnRect . height / 2 ;
73
77
const placeAbove = buttonCenter > screenHeight / 2 ;
74
-
75
- // rely on CSS classes and the parent .tw-relative for positioning
76
78
modal . classList . remove (
77
79
"tw-top-full" ,
78
80
"tw-bottom-full" ,
@@ -81,22 +83,18 @@ document.addEventListener("DOMContentLoaded", () => {
81
83
) ;
82
84
if ( placeAbove ) {
83
85
modal . classList . add ( "tw-bottom-full" , "tw-mb-4" ) ;
84
- // explicitly position above using inline style to avoid CSS specificity issues
85
86
modal . style . bottom = "100%" ;
86
87
modal . style . top = "" ;
87
88
} else {
88
89
modal . classList . add ( "tw-top-full" , "tw-mt-4" ) ;
89
- // explicitly position below
90
90
modal . style . top = "100%" ;
91
91
modal . style . bottom = "" ;
92
92
}
93
- // ensure right alignment like Vue: right-0 on the modal container
94
93
if ( ! modal . classList . contains ( "tw-right-0" ) )
95
94
modal . classList . add ( "tw-right-0" ) ;
96
95
} catch ( err ) { }
97
96
}
98
97
99
- // wire tab clicks with keyboard navigation and ARIA handling
100
98
if ( tabs && tabs . length ) {
101
99
const setActiveTab = ( index ) => {
102
100
tabs . forEach ( ( tb , i ) => {
@@ -137,7 +135,6 @@ document.addEventListener("DOMContentLoaded", () => {
137
135
} ) ;
138
136
} ) ;
139
137
140
- // init
141
138
setActiveTab ( 0 ) ;
142
139
}
143
140
@@ -165,56 +162,34 @@ document.addEventListener("DOMContentLoaded", () => {
165
162
166
163
form . addEventListener ( "submit" , ( e ) => {
167
164
e . preventDefault ( ) ;
168
-
169
- // First, let the browser run HTML5 validation UI (native popup) if any
170
- // required fields are missing. reportValidity() will show the native
171
- // validation message and return false if invalid.
172
165
if ( typeof form . reportValidity === "function" ) {
173
166
const ok = form . reportValidity ( ) ;
174
167
if ( ! ok ) {
175
- // browser showed a native message; stop submission
176
168
return ;
177
169
}
178
170
}
179
-
180
- // hide any previous custom error
181
171
try {
182
172
errorView . classList . add ( "tw-hidden" ) ;
183
- } catch ( err ) {
184
- /* ignore */
185
- }
186
-
187
- // grab textarea and read trimmed value (we already know it's non-empty)
173
+ } catch ( err ) { }
188
174
const ta =
189
175
form . querySelector ( "textarea" ) || modal . querySelector ( "textarea" ) ;
190
176
const message = ( ta && ta . value && ta . value . trim ( ) ) || "" ;
191
-
192
177
const data = {
193
- // use the prepared hidden input value (always present)
194
178
type : ( typeInput && typeInput . value ) || "Issue" ,
195
179
message : message ,
196
180
currentUrl : window . location . href ,
197
181
userAgent : navigator . userAgent ,
198
182
source : "feedback_form" ,
199
183
} ;
200
-
201
- // Track feedback in Segment (if segment.js is loaded)
202
184
if ( typeof window . trackFeedback === "function" ) {
203
185
try {
204
186
window . trackFeedback ( data ) ;
205
- } catch ( e ) {
206
- // Segment tracking error should not block submission
207
- }
187
+ } catch ( e ) { }
208
188
}
209
-
210
- // show immediate success view (keeps original UX), then submit in background
211
189
formView . classList . add ( "tw-hidden" ) ;
212
- // ensure success view displays as flex column when visible
213
190
successView . classList . add ( "tw-flex" ) ;
214
191
successView . classList . remove ( "tw-hidden" ) ;
215
-
216
192
setTimeout ( closeModal , 1500 ) ;
217
-
218
193
fetch (
219
194
"https://script.google.com/macros/s/AKfycby5A7NSQCmG4KIBdM0HkRP-5zpRPy8aTrQHiQoe9uG_c_rv1VCiAnnZE8co7-kofgw-hg/exec" ,
220
195
{
@@ -224,7 +199,6 @@ document.addEventListener("DOMContentLoaded", () => {
224
199
headers : { "Content-Type" : "application/json" } ,
225
200
}
226
201
) . catch ( ( ) => {
227
- // network failure: hide success and show error
228
202
try {
229
203
successView . classList . add ( "tw-hidden" ) ;
230
204
successView . classList . remove ( "tw-flex" ) ;
@@ -235,9 +209,18 @@ document.addEventListener("DOMContentLoaded", () => {
235
209
errorView . classList . remove ( "tw-hidden" ) ;
236
210
}
237
211
if ( ta && typeof ta . focus === "function" ) ta . focus ( ) ;
238
- } catch ( err ) {
239
- /* ignore */
240
- }
212
+ } catch ( err ) { }
241
213
} ) ;
242
214
} ) ;
215
+ }
216
+
217
+ // Run on DOMContentLoaded and MkDocs instant navigation
218
+ if ( typeof window . document$ !== "undefined" ) {
219
+ window . document$ . subscribe ( ( ) => {
220
+ setTimeout ( feedbackModalInit , 0 ) ;
221
+ } ) ;
222
+ }
223
+ // Always run on DOMContentLoaded (for initial load)
224
+ document . addEventListener ( "DOMContentLoaded" , ( ) => {
225
+ setTimeout ( feedbackModalInit , 0 ) ;
243
226
} ) ;
0 commit comments