File size: 10,641 Bytes
4d2f95d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
# π Bug Fix: Edit Button Accidentally Triggers Seek
## Problem Description
### Original Issue
When clicking the edit button (βοΈ) or clicking inside the textarea while editing an utterance, the click event would bubble up and trigger the `seekToTime()` function, causing unwanted audio player behavior:
1. **Click on Edit button** β Sometimes triggers seek if click slightly off-target
2. **Click on textarea** β Triggers seek, interrupting editing
3. **Click on Save/Cancel buttons** β Triggers seek
### User Impact
- π Disorienting: Audio jumps unexpectedly while trying to edit
- π€ Frustrating: Can't select text in textarea without triggering seek
- π Poor UX: Edit workflow is interrupted
---
## Root Cause Analysis
### Event Bubbling Problem
```
HTML Structure:
βββββββββββββββββββββββββββββββββββββββ
β .utterance-item (click listener) β β Event listener here
β ββββββββββββββββββββββββββββββββββ β
β β .utterance-header β β
β β [timestamp] [speaker] [βοΈ] β β
β ββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββ β
β β .utterance-text β β
β ββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββ β
β β .edit-area (hidden) β β
β β <textarea>...</textarea> β β
β β [Save] [Cancel] β β
β ββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ
```
### Event Flow (Before Fix)
```
User clicks on textarea
β
Click event on <textarea>
β
Bubbles to .edit-area
β
Bubbles to .utterance-item β Listener catches it here
β
Checks: editButton? No
Checks: saveButton? No
Checks: cancelButton? No
β
β Falls through to seekToTime(start)
β
π΅ Audio jumps to utterance start time
```
---
## Solution Implemented
### Two-Pronged Approach
#### 1. **Event Propagation Control**
Added `event.stopPropagation()` on all edit-related buttons to prevent event bubbling:
```javascript
if (editButton) {
event.stopPropagation(); // β
Stop bubbling
toggleEdit(item, true);
return;
}
if (saveButton) {
event.stopPropagation(); // β
Stop bubbling
// ... save logic ...
return;
}
if (cancelButton) {
event.stopPropagation(); // β
Stop bubbling
toggleEdit(item, false);
return;
}
```
#### 2. **Edit Area Detection**
Added explicit checks for clicks within the edit area or textarea:
```javascript
const editArea = event.target.closest('.edit-area');
const textarea = event.target.closest('textarea');
// Prevent seek when clicking on edit area or textarea
if (editArea || textarea) {
return; // β
Do nothing, allow text selection/editing
}
```
---
## Event Flow (After Fix)
### Scenario 1: Click Edit Button
```
User clicks edit button βοΈ
β
Click event on .edit-btn
β
Bubbles to .utterance-item
β
Checks: editButton? Yes
β
event.stopPropagation() β
Stop here!
β
toggleEdit(item, true)
β
β
Edit mode activated, no seek
```
### Scenario 2: Click on Textarea
```
User clicks inside textarea
β
Click event on <textarea>
β
Bubbles to .edit-area
β
Bubbles to .utterance-item
β
Checks: editArea? Yes
β
return; β
Stop here!
β
β
Text selection works, no seek
```
### Scenario 3: Click on Save Button
```
User clicks Save button
β
Click event on .save-edit
β
Bubbles to .utterance-item
β
Checks: saveButton? Yes
β
event.stopPropagation() β
Stop here!
β
Save text + toggleEdit(item, false)
β
β
Edit saved, no seek
```
### Scenario 4: Click on Utterance Text (Normal Seek)
```
User clicks on .utterance-text
β
Click event on .utterance-text
β
Bubbles to .utterance-item
β
Checks: editButton? No
Checks: saveButton? No
Checks: cancelButton? No
Checks: editArea? No
Checks: textarea? No
β
Falls through to seekToTime(start)
β
β
Audio seeks as expected
```
---
## Code Changes
### File: `frontend/app.js`
```javascript
elements.transcriptList.addEventListener('click', (event) => {
const item = event.target.closest('.utterance-item');
if (!item) return;
// β¨ NEW: Additional selectors for edit-related elements
const editButton = event.target.closest('.edit-btn');
const saveButton = event.target.closest('.save-edit');
const cancelButton = event.target.closest('.cancel-edit');
const speakerTag = event.target.closest('.editable-speaker');
const editArea = event.target.closest('.edit-area'); // NEW
const textarea = event.target.closest('textarea'); // NEW
const index = Number(item.dataset.index);
// Handle speaker tag editing
if (speakerTag && !speakerTag.querySelector('input')) {
startSpeakerEdit(speakerTag);
return;
}
// β¨ MODIFIED: Stop propagation on edit button
if (editButton) {
event.stopPropagation(); // NEW
toggleEdit(item, true);
return;
}
// β¨ MODIFIED: Stop propagation on save button
if (saveButton) {
event.stopPropagation(); // NEW
const textarea = item.querySelector('textarea');
const newText = textarea.value.trim();
if (newText.length === 0) return;
state.utterances[index].text = newText;
item.querySelector('.utterance-text').textContent = newText;
toggleEdit(item, false);
return;
}
// β¨ MODIFIED: Stop propagation on cancel button
if (cancelButton) {
event.stopPropagation(); // NEW
toggleEdit(item, false);
return;
}
// β¨ NEW: Prevent seek when clicking on edit area or textarea
if (editArea || textarea) {
return; // Do nothing, allow text selection/editing
}
// Default behavior: seek to utterance start time
const start = Number(item.dataset.start);
seekToTime(start);
});
```
---
## Testing Scenarios
### β
Test 1: Edit Button Click
```
Steps:
1. Click the edit button (βοΈ) on any utterance
2. Observe audio player
Expected:
- Edit mode activates
- Textarea appears
- Audio player does NOT seek
Result: β
Pass
```
### β
Test 2: Click Inside Textarea
```
Steps:
1. Enter edit mode
2. Click inside the textarea to position cursor
3. Try to select text
4. Observe audio player
Expected:
- Cursor positioning works
- Text selection works
- Audio player does NOT seek
Result: β
Pass
```
### β
Test 3: Save Button Click
```
Steps:
1. Enter edit mode
2. Modify text
3. Click Save button
4. Observe audio player
Expected:
- Changes saved
- Edit mode exits
- Audio player does NOT seek
Result: β
Pass
```
### β
Test 4: Cancel Button Click
```
Steps:
1. Enter edit mode
2. Modify text (don't save)
3. Click Cancel button
4. Observe audio player
Expected:
- Changes discarded
- Edit mode exits
- Audio player does NOT seek
Result: β
Pass
```
### β
Test 5: Normal Seek (Not in Edit Mode)
```
Steps:
1. Ensure no utterance is in edit mode
2. Click on utterance text or timestamp
3. Observe audio player
Expected:
- Audio player seeks to utterance start time
- Audio starts playing
Result: β
Pass
```
### β
Test 6: Edge Case - Click on Edit Area Border
```
Steps:
1. Enter edit mode
2. Click on the border/padding of edit area (not textarea)
3. Observe audio player
Expected:
- No seek triggered
- Edit mode remains active
Result: β
Pass
```
---
## Impact Summary
| Aspect | Before | After | Improvement |
|--------|--------|-------|-------------|
| **Edit button usability** | Unreliable | Reliable | β
Fixed |
| **Textarea interaction** | Triggers seek | Works normally | β
Fixed |
| **Save/Cancel buttons** | May trigger seek | No seek | β
Fixed |
| **Normal seek behavior** | Works | Still works | β
Preserved |
| **Event handling** | Single check | Multi-layered | π‘οΈ More robust |
| **Code clarity** | Implicit | Explicit | π Better |
---
## Technical Details
### Why `event.stopPropagation()`?
`stopPropagation()` prevents the event from bubbling up the DOM tree, which is crucial when:
- Child elements need different behavior than parent
- Multiple nested click handlers exist
- You want fine-grained control over event handling
### Why `closest()` Selector?
```javascript
const editArea = event.target.closest('.edit-area');
```
`closest()` traverses up the DOM tree to find the first matching ancestor, which:
- β
Catches clicks on any descendant of `.edit-area`
- β
Works even if clicking on nested elements (textarea, buttons)
- β
More reliable than checking `event.target` directly
### Alternative Approaches (Not Used)
β **Approach 1: Separate listeners on each button**
- Pro: More explicit
- Con: Harder to maintain, more memory
β **Approach 2: Check if edit mode is active**
- Pro: Simple boolean check
- Con: Doesn't handle clicks on buttons themselves
β
**Approach 3: Multi-layered detection (Chosen)**
- Pro: Handles all cases elegantly
- Pro: Maintains single event listener
- Pro: Easy to understand and debug
---
## Browser Compatibility
| Feature | Support |
|---------|---------|
| `event.stopPropagation()` | β
All browsers |
| `element.closest()` | β
All modern browsers (IE9+) |
| Event bubbling | β
All browsers |
---
## Future Improvements (Optional)
1. **Visual feedback on buttons**
- Add hover states to make clickable areas more obvious
- Increase button size for better touch targets
2. **Keyboard shortcuts**
- `Enter` to save (with Ctrl/Cmd modifier)
- `Escape` to cancel
- Already implemented for speaker name editing
3. **Double-click to edit**
- Alternative to clicking edit button
- Single click = seek, double click = edit
---
## Conclusion
This fix provides a robust solution to prevent accidental seeks during editing by:
1. β
Stopping event propagation on all edit-related buttons
2. β
Detecting clicks within the edit area
3. β
Preserving normal seek behavior for non-edit interactions
4. β
Maintaining clean, maintainable code
The edit workflow is now smooth and intuitive! π
|