Tools for the Atmosphere tools.slices.network
quickslice atproto html

refactor(bugs): use BugsApp namespace for inline event handlers

Replace Object.assign(window, {...}) with window.BugsApp namespace
to expose functions for inline event handlers. This aligns with the
pattern used in docs.html and reduces global scope pollution.

+147 -80
+147 -80
bugs.html
··· 1399 1399 1400 1400 function showError(msg) { 1401 1401 const el = document.getElementById("error-banner"); 1402 - el.innerHTML = `<span>${esc(msg)}</span><button onclick="hideError()">×</button>`; 1402 + el.innerHTML = `<span>${esc(msg)}</span><button onclick="BugsApp.hideError()">×</button>`; 1403 1403 el.classList.remove("hidden"); 1404 1404 } 1405 1405 ··· 1544 1544 errorEl.innerHTML = ` 1545 1545 This looks like a domain. Did you mean <strong>${esc(suggested)}</strong>? 1546 1546 <div style="margin-top: 0.5rem;"> 1547 - <button type="button" class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="useSuggestedNamespace('${esc(suggested)}')">Use ${esc(suggested)}</button> 1548 - <button type="button" class="btn btn-secondary" style="margin-left: 0.25rem; padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="dismissNamespaceSuggestion()">Keep as-is</button> 1547 + <button type="button" class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="BugsApp.useSuggestedNamespace('${esc(suggested)}')">Use ${esc(suggested)}</button> 1548 + <button type="button" class="btn btn-secondary" style="margin-left: 0.25rem; padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="BugsApp.dismissNamespaceSuggestion()">Keep as-is</button> 1549 1549 </div> 1550 1550 `; 1551 1551 input.parentElement.classList.add("has-error"); ··· 2031 2031 return ` 2032 2032 <div class="loading-container"> 2033 2033 <p>No bug reports yet.</p> 2034 - <button class="btn btn-primary" onclick="handleReportBug()">Report First Bug</button> 2034 + <button class="btn btn-primary" onclick="BugsApp.handleReportBug()">Report First Bug</button> 2035 2035 </div> 2036 2036 `; 2037 2037 } 2038 2038 2039 2039 return ` 2040 - <div class="filter-bar" style="justify-content: flex-end;"><button class="btn btn-primary" onclick="handleReportBug()">Report Bug</button></div> 2040 + <div class="filter-bar" style="justify-content: flex-end;"><button class="btn btn-primary" onclick="BugsApp.handleReportBug()">Report Bug</button></div> 2041 2041 <div class="namespace-grid"> 2042 2042 ${state.namespaces 2043 2043 .map( 2044 2044 (ns) => ` 2045 - <div class="card namespace-card" onclick="navigateToNamespace('${esc(ns.namespace)}')"> 2045 + <div class="card namespace-card" onclick="BugsApp.navigateToNamespace('${esc(ns.namespace)}')"> 2046 2046 <div class="namespace-name">${esc(ns.namespace)}</div> 2047 2047 <div class="namespace-count">${ns.count} bug${ns.count !== 1 ? "s" : ""}</div> 2048 2048 </div> ··· 2075 2075 content += ` 2076 2076 <div class="filter-bar"> 2077 2077 <div class="filter-group"> 2078 - <select onchange="handleSeverityFilter(this.value)"> 2078 + <select onchange="BugsApp.handleSeverityFilter(this.value)"> 2079 2079 <option value="">All Severities</option> 2080 2080 <option value="unusable" ${state.filters.severity === "unusable" ? "selected" : ""}>Unusable</option> 2081 2081 <option value="broken" ${state.filters.severity === "broken" ? "selected" : ""}>Broken</option> ··· 2083 2083 <option value="cosmetic" ${state.filters.severity === "cosmetic" ? "selected" : ""}>Cosmetic</option> 2084 2084 </select> 2085 2085 </div> 2086 - <button class="btn btn-primary" onclick="handleReportBug()">Report Bug</button> 2086 + <button class="btn btn-primary" onclick="BugsApp.handleReportBug()">Report Bug</button> 2087 2087 </div> 2088 2088 `; 2089 2089 ··· 2115 2115 if (state.hasMore) { 2116 2116 content += ` 2117 2117 <div class="load-more"> 2118 - <button class="btn btn-secondary" onclick="loadMoreBugs()" ${state.isLoading ? "disabled" : ""}> 2118 + <button class="btn btn-secondary" onclick="BugsApp.loadMoreBugs()" ${state.isLoading ? "disabled" : ""}> 2119 2119 ${state.isLoading ? '<span class="spinner" style="width:16px;height:16px;border-width:2px;display:inline-block;vertical-align:middle;"></span>' : "Load More"} 2120 2120 </button> 2121 2121 </div> ··· 2130 2130 const displayStatus = getBugDisplayStatus(bug); 2131 2131 const commentCount = bug.networkSlicesToolsBugCommentViaBug?.totalCount || 0; 2132 2132 return ` 2133 - <div class="card bug-card" onclick="openBugDetail('${esc(bug.uri)}')"> 2133 + <div class="card bug-card" onclick="BugsApp.openBugDetail('${esc(bug.uri)}')"> 2134 2134 <div class="bug-card-header"> 2135 2135 <span class="badge ${getSeverityClass(bug.severity)}">${esc(bug.severity)}</span> 2136 2136 <span class="status-badge ${displayStatus.cssClass}">${esc(displayStatus.label)}</span> ··· 2191 2191 overlay.innerHTML = ` 2192 2192 <div class="overlay-header"> 2193 2193 <span class="overlay-title">Loading...</span> 2194 - <button class="overlay-close" onclick="closeOverlay()">×</button> 2194 + <button class="overlay-close" onclick="BugsApp.closeOverlay()">×</button> 2195 2195 </div> 2196 2196 <div class="overlay-body"> 2197 2197 <div class="loading-container"> ··· 2235 2235 <span class="status-badge ${displayStatus.cssClass}">${esc(displayStatus.label)}</span> 2236 2236 <h2 class="overlay-title">${esc(bug.title)}</h2> 2237 2237 </div> 2238 - <button class="overlay-close" onclick="closeOverlay()">×</button> 2238 + <button class="overlay-close" onclick="BugsApp.closeOverlay()">×</button> 2239 2239 </div> 2240 2240 <div class="overlay-body"> 2241 2241 <div class="overlay-meta"> ··· 2245 2245 </div> 2246 2246 2247 2247 <div class="overlay-actions" style="margin-bottom: 1.5rem;"> 2248 - <button class="btn btn-secondary" onclick="shareBug()">Share</button> 2249 - ${canAddResponse ? `<button class="btn btn-secondary btn-icon-text" onclick="openLinkIssueModal('${esc(bug.uri)}')">${tangledIcon(16)} Link Issue</button>` : ""} 2248 + <button class="btn btn-secondary" onclick="BugsApp.shareBug()">Share</button> 2249 + ${canAddResponse ? `<button class="btn btn-secondary btn-icon-text" onclick="BugsApp.openLinkIssueModal('${esc(bug.uri)}')">${tangledIcon(16)} Link Issue</button>` : ""} 2250 2250 ${ 2251 2251 canEdit 2252 2252 ? ` 2253 - <button class="btn btn-secondary" onclick="openEditModal('${esc(bug.uri)}')">Edit</button> 2254 - <button class="btn btn-danger" onclick="handleDeleteBug('${esc(bug.uri)}')">Delete</button> 2253 + <button class="btn btn-secondary" onclick="BugsApp.openEditModal('${esc(bug.uri)}')">Edit</button> 2254 + <button class="btn btn-danger" onclick="BugsApp.handleDeleteBug('${esc(bug.uri)}')">Delete</button> 2255 2255 ` 2256 2256 : "" 2257 2257 } ··· 2317 2317 <span class="status-badge ${displayStatus.cssClass}">${esc(displayStatus.label)}</span> 2318 2318 <h2 class="overlay-title">${esc(bug.title)}</h2> 2319 2319 </div> 2320 - <button class="overlay-close" onclick="closeOverlay()">×</button> 2320 + <button class="overlay-close" onclick="BugsApp.closeOverlay()">×</button> 2321 2321 </div> 2322 2322 <div class="overlay-body"> 2323 2323 <div class="overlay-meta"> ··· 2327 2327 </div> 2328 2328 2329 2329 <div class="overlay-actions" style="margin-bottom: 1.5rem;"> 2330 - <button class="btn btn-secondary" onclick="shareBug()">Share</button> 2331 - ${canAddResponse ? `<button class="btn btn-secondary btn-icon-text" onclick="openLinkIssueModal('${esc(bug.uri)}')">${tangledIcon(16)} Link Issue</button>` : ""} 2330 + <button class="btn btn-secondary" onclick="BugsApp.shareBug()">Share</button> 2331 + ${canAddResponse ? `<button class="btn btn-secondary btn-icon-text" onclick="BugsApp.openLinkIssueModal('${esc(bug.uri)}')">${tangledIcon(16)} Link Issue</button>` : ""} 2332 2332 ${ 2333 2333 canEdit 2334 2334 ? ` 2335 - <button class="btn btn-secondary" onclick="openEditModal('${esc(bug.uri)}')">Edit</button> 2336 - <button class="btn btn-danger" onclick="handleDeleteBug('${esc(bug.uri)}')">Delete</button> 2335 + <button class="btn btn-secondary" onclick="BugsApp.openEditModal('${esc(bug.uri)}')">Edit</button> 2336 + <button class="btn btn-danger" onclick="BugsApp.handleDeleteBug('${esc(bug.uri)}')">Delete</button> 2337 2337 ` 2338 2338 : "" 2339 2339 } ··· 2418 2418 content = ` 2419 2419 <div class="modal-header"> 2420 2420 <h2 style="display: flex; align-items: center; gap: 0.5rem;">${tangledIcon(20)} Link to Tangled Issue</h2> 2421 - <button class="modal-close" onclick="closeLinkIssueModal()">×</button> 2421 + <button class="modal-close" onclick="BugsApp.closeLinkIssueModal()">×</button> 2422 2422 </div> 2423 2423 <div class="modal-body"> 2424 2424 ${ ··· 2439 2439 : ` 2440 2440 <div class="form-group"> 2441 2441 <label for="repo-select">Select a repository:</label> 2442 - <select id="repo-select" onchange="if(this.value) selectRepoForLinking(this.value)"> 2442 + <select id="repo-select" onchange="if(this.value) BugsApp.selectRepoForLinking(this.value)"> 2443 2443 <option value="">Choose a repo...</option> 2444 2444 ${repos 2445 2445 .map( ··· 2459 2459 content = ` 2460 2460 <div class="modal-header"> 2461 2461 <h2 style="display: flex; align-items: center; gap: 0.5rem;">${tangledIcon(20)} Link to Tangled Issue</h2> 2462 - <button class="modal-close" onclick="closeLinkIssueModal()">×</button> 2462 + <button class="modal-close" onclick="BugsApp.closeLinkIssueModal()">×</button> 2463 2463 </div> 2464 2464 <div class="modal-body"> 2465 - <button class="btn btn-secondary" onclick="goBackToRepos()" style="margin-bottom: 1rem;">← Back to repos</button> 2465 + <button class="btn btn-secondary" onclick="BugsApp.goBackToRepos()" style="margin-bottom: 1rem;">← Back to repos</button> 2466 2466 <p style="margin-bottom: 0.5rem;"><strong>${esc(selectedRepo.name)}</strong></p> 2467 2467 2468 - <button class="btn btn-primary" style="width: 100%; margin-bottom: 1rem;" onclick="createAndLinkIssue()" ${isLoading ? "disabled" : ""}> 2468 + <button class="btn btn-primary" style="width: 100%; margin-bottom: 1rem;" onclick="BugsApp.createAndLinkIssue()" ${isLoading ? "disabled" : ""}> 2469 2469 + Create New Issue from Bug 2470 2470 </button> 2471 2471 ··· 2487 2487 ${issues 2488 2488 .map( 2489 2489 (issue) => ` 2490 - <div class="card" style="cursor: pointer;" onclick="linkToExistingIssue('${esc(issue.uri)}')"> 2490 + <div class="card" style="cursor: pointer;" onclick="BugsApp.linkToExistingIssue('${esc(issue.uri)}')"> 2491 2491 <div style="font-weight: 500;">${esc(issue.title)}</div> 2492 2492 <div class="text-secondary" style="font-size: 0.75rem;">${formatTime(issue.createdAt)}</div> 2493 2493 </div> ··· 2502 2502 } 2503 2503 2504 2504 modal.innerHTML = ` 2505 - <div class="modal-backdrop" onclick="closeLinkIssueModal()"></div> 2505 + <div class="modal-backdrop" onclick="BugsApp.closeLinkIssueModal()"></div> 2506 2506 <div class="modal-content scrollable"> 2507 2507 ${content} 2508 2508 </div> ··· 2689 2689 ${url ? ` · <a href="${esc(url)}" target="_blank" onclick="event.stopPropagation()">View on Tangled</a>` : ""} 2690 2690 </div> 2691 2691 </div> 2692 - ${canUnlink ? `<button class="btn-icon btn-danger-text" onclick="unlinkIssue('${esc(link.uri)}')" title="Unlink">×</button>` : ""} 2692 + ${canUnlink ? `<button class="btn-icon btn-danger-text" onclick="BugsApp.unlinkIssue('${esc(link.uri)}')" title="Unlink">×</button>` : ""} 2693 2693 </div> 2694 2694 `; 2695 2695 }) ··· 2739 2739 class="attachment-image" 2740 2740 src="${esc(img.image.url)}" 2741 2741 alt="${esc(img.alt || "Bug attachment")}" 2742 - onclick="openLightbox('${esc(img.image.url)}')" 2742 + onclick="BugsApp.openLightbox('${esc(img.image.url)}')" 2743 2743 /> 2744 2744 `, 2745 2745 ) ··· 2764 2764 <div class="response-header"> 2765 2765 <span class="status-badge ${getStatusClass(response.status)}">${esc(response.status)}</span> 2766 2766 <span class="response-meta"><span class="user-info">${renderAvatar(response.appBskyActorProfileByDid, response.actorHandle, "user-avatar-sm")}@${esc(response.actorHandle)}</span> · ${formatTime(response.createdAt)}</span> 2767 - ${isResponseAuthor ? `<button class="btn-icon btn-danger-text" onclick="handleDeleteResponse('${esc(response.uri)}')" title="Delete response">×</button>` : ""} 2767 + ${isResponseAuthor ? `<button class="btn-icon btn-danger-text" onclick="BugsApp.handleDeleteResponse('${esc(response.uri)}')" title="Delete response">×</button>` : ""} 2768 2768 </div> 2769 2769 ${response.message ? `<p class="response-message">${renderFacetedText(response.message, response.messageFacets, { escapeHtml: esc })}</p>` : ""} 2770 2770 </div> ··· 2775 2775 return ` 2776 2776 <div class="overlay-section"> 2777 2777 <h3>Add Response</h3> 2778 - <form onsubmit="handleSubmitResponse(event)"> 2778 + <form onsubmit="BugsApp.handleSubmitResponse(event)"> 2779 2779 <div class="form-group"> 2780 2780 <label for="response-status">Status</label> 2781 2781 <select id="response-status" required> ··· 2813 2813 (img, i) => ` 2814 2814 <div class="image-preview"> 2815 2815 <img src="${esc(img.image?.url)}" alt="${esc(img.alt || "")}" style="max-width: 80px; max-height: 80px; width: auto; height: auto;"> 2816 - <button type="button" onclick="removeEditCommentAttachment(${i})">×</button> 2816 + <button type="button" onclick="BugsApp.removeEditCommentAttachment(${i})">×</button> 2817 2817 </div> 2818 2818 `, 2819 2819 ) ··· 2825 2825 return ` 2826 2826 <div class="comment-card" data-uri="${esc(comment.uri)}"> 2827 2827 <div class="comment-form-inline" style="border-top: none; margin-top: 0; padding-top: 0;"> 2828 - <form onsubmit="handleSaveEditComment(event, '${esc(comment.uri)}')"> 2828 + <form onsubmit="BugsApp.handleSaveEditComment(event, '${esc(comment.uri)}')"> 2829 2829 <textarea id="edit-comment-${esc(comment.uri)}" required>${esc(comment.body)}</textarea> 2830 2830 ${attachmentPreviews} 2831 2831 <div class="comment-form-actions"> 2832 - <button type="button" class="btn btn-secondary" onclick="cancelEditComment()">Cancel</button> 2832 + <button type="button" class="btn btn-secondary" onclick="BugsApp.cancelEditComment()">Cancel</button> 2833 2833 <button type="submit" class="btn btn-primary">Save</button> 2834 2834 </div> 2835 2835 </form> ··· 2846 2846 canEdit 2847 2847 ? ` 2848 2848 <div> 2849 - <button class="btn-icon" onclick="startEditComment('${esc(comment.uri)}')" title="Edit"><i data-lucide="pencil"></i></button> 2850 - <button class="btn-icon btn-danger-text" onclick="handleDeleteComment('${esc(comment.uri)}')" title="Delete"><i data-lucide="trash-2"></i></button> 2849 + <button class="btn-icon" onclick="BugsApp.startEditComment('${esc(comment.uri)}')" title="Edit"><i data-lucide="pencil"></i></button> 2850 + <button class="btn-icon btn-danger-text" onclick="BugsApp.handleDeleteComment('${esc(comment.uri)}')" title="Delete"><i data-lucide="trash-2"></i></button> 2851 2851 </div> 2852 2852 ` 2853 2853 : "" ··· 2859 2859 !isReply && canComment() 2860 2860 ? ` 2861 2861 <div class="comment-actions"> 2862 - <button class="btn btn-secondary" onclick="showReplyForm('${esc(comment.uri)}')">Reply</button> 2862 + <button class="btn btn-secondary" onclick="BugsApp.showReplyForm('${esc(comment.uri)}')">Reply</button> 2863 2863 </div> 2864 2864 ` 2865 2865 : "" ··· 2882 2882 class="attachment-image" 2883 2883 src="${esc(img.image.url)}" 2884 2884 alt="${esc(img.alt || "Comment attachment")}" 2885 - onclick="openLightbox('${esc(img.image.url)}')" 2885 + onclick="BugsApp.openLightbox('${esc(img.image.url)}')" 2886 2886 /> 2887 2887 `, 2888 2888 ) ··· 2894 2894 function renderReplyForm(parentUri) { 2895 2895 return ` 2896 2896 <div class="comment-form-inline"> 2897 - <form onsubmit="handleSubmitComment(event, '${esc(parentUri)}')"> 2897 + <form onsubmit="BugsApp.handleSubmitComment(event, '${esc(parentUri)}')"> 2898 2898 <textarea id="reply-body" placeholder="Write a reply..." required></textarea> 2899 2899 ${renderCommentImagePreviews()} 2900 2900 <div class="comment-form-actions"> 2901 - <button type="button" class="btn btn-secondary" onclick="cancelReply()">Cancel</button> 2902 - <input type="file" id="reply-images" accept="image/*" multiple onchange="handleCommentImageSelect(event)" style="display: none;"> 2901 + <button type="button" class="btn btn-secondary" onclick="BugsApp.cancelReply()">Cancel</button> 2902 + <input type="file" id="reply-images" accept="image/*" multiple onchange="BugsApp.handleCommentImageSelect(event)" style="display: none;"> 2903 2903 <button type="button" class="btn btn-secondary btn-icon" onclick="document.getElementById('reply-images').click()" title="Add images"> 2904 2904 <i data-lucide="image"></i> 2905 2905 </button> ··· 2943 2943 function renderCommentForm() { 2944 2944 return ` 2945 2945 <div class="comment-form-inline" style="border-top: none; margin-top: 1rem;"> 2946 - <form onsubmit="handleSubmitComment(event)"> 2946 + <form onsubmit="BugsApp.handleSubmitComment(event)"> 2947 2947 <textarea id="comment-body" placeholder="Add a comment..." required></textarea> 2948 2948 ${renderCommentImagePreviews()} 2949 2949 <div class="comment-form-actions"> 2950 - <input type="file" id="comment-images" accept="image/*" multiple onchange="handleCommentImageSelect(event)" style="display: none;"> 2950 + <input type="file" id="comment-images" accept="image/*" multiple onchange="BugsApp.handleCommentImageSelect(event)" style="display: none;"> 2951 2951 <button type="button" class="btn btn-secondary btn-icon" onclick="document.getElementById('comment-images').click()" title="Add images"> 2952 2952 <i data-lucide="image"></i> 2953 2953 </button> ··· 2967 2967 (img, i) => ` 2968 2968 <div class="image-preview"> 2969 2969 <img src="${img.preview}" alt="Preview"> 2970 - <button type="button" onclick="removeCommentImage(${i})">×</button> 2970 + <button type="button" onclick="BugsApp.removeCommentImage(${i})">×</button> 2971 2971 </div> 2972 2972 `, 2973 2973 ) ··· 3284 3284 3285 3285 const modal = document.getElementById("modal"); 3286 3286 modal.innerHTML = ` 3287 - <div class="modal-backdrop" onclick="closeModal()"></div> 3287 + <div class="modal-backdrop" onclick="BugsApp.closeModal()"></div> 3288 3288 <div class="modal-content scrollable"> 3289 3289 <div class="modal-header"> 3290 3290 <h2>Report Bug</h2> 3291 - <button class="modal-close" onclick="closeModal()">×</button> 3291 + <button class="modal-close" onclick="BugsApp.closeModal()">×</button> 3292 3292 </div> 3293 3293 <div class="modal-body"> 3294 - <form id="bug-form" onsubmit="handleSubmitBug(event)"> 3294 + <form id="bug-form" onsubmit="BugsApp.handleSubmitBug(event)"> 3295 3295 <div class="form-group"> 3296 3296 <label for="bug-title">Title *</label> 3297 3297 <input type="text" id="bug-title" required maxlength="100" placeholder="Brief description of the issue"> ··· 3300 3300 <div class="form-group"> 3301 3301 <label for="bug-namespace">Namespace *</label> 3302 3302 <p class="hint">e.g., social.grain, app.bsky, fm.teal</p> 3303 - <input type="text" id="bug-namespace" required placeholder="com.example" value="${esc(state.namespace || "")}" onblur="validateNamespaceOnBlur()"> 3303 + <input type="text" id="bug-namespace" required placeholder="com.example" value="${esc(state.namespace || "")}" onblur="BugsApp.validateNamespaceOnBlur()"> 3304 3304 <div class="error" id="namespace-error"></div> 3305 3305 </div> 3306 3306 ··· 3335 3335 <div class="form-group"> 3336 3336 <label>Screenshots (optional)</label> 3337 3337 <div class="image-upload" onclick="document.getElementById('bug-images').click()"> 3338 - <input type="file" id="bug-images" accept="image/*" multiple onchange="handleImageSelect(event)"> 3338 + <input type="file" id="bug-images" accept="image/*" multiple onchange="BugsApp.handleImageSelect(event)"> 3339 3339 <p>Click to upload images</p> 3340 3340 </div> 3341 3341 <div class="image-previews" id="image-previews"></div> 3342 3342 </div> 3343 3343 3344 3344 <div class="form-actions"> 3345 - <button type="button" class="btn btn-secondary" onclick="closeModal()">Cancel</button> 3345 + <button type="button" class="btn btn-secondary" onclick="BugsApp.closeModal()">Cancel</button> 3346 3346 <button type="submit" class="btn btn-primary" id="submit-bug-btn">Submit Bug</button> 3347 3347 </div> 3348 3348 </form> ··· 3364 3364 function openInfoModal() { 3365 3365 const modal = document.getElementById("modal"); 3366 3366 modal.innerHTML = ` 3367 - <div class="modal-backdrop" onclick="closeModal()"></div> 3367 + <div class="modal-backdrop" onclick="BugsApp.closeModal()"></div> 3368 3368 <div class="modal-content scrollable"> 3369 3369 <div class="modal-header"> 3370 3370 <h2>How Bug Tracker Works</h2> 3371 - <button class="modal-close" onclick="closeModal()">×</button> 3371 + <button class="modal-close" onclick="BugsApp.closeModal()">×</button> 3372 3372 </div> 3373 3373 <div class="modal-body"> 3374 3374 <div class="info-section"> ··· 3441 3441 3442 3442 const modal = document.getElementById("modal"); 3443 3443 modal.innerHTML = ` 3444 - <div class="modal-backdrop" onclick="closeModal()"></div> 3444 + <div class="modal-backdrop" onclick="BugsApp.closeModal()"></div> 3445 3445 <div class="modal-content scrollable"> 3446 3446 <div class="modal-header"> 3447 3447 <h2>Edit Bug</h2> 3448 - <button class="modal-close" onclick="closeModal()">×</button> 3448 + <button class="modal-close" onclick="BugsApp.closeModal()">×</button> 3449 3449 </div> 3450 3450 <div class="modal-body"> 3451 - <form id="bug-form" onsubmit="handleEditBug(event)"> 3451 + <form id="bug-form" onsubmit="BugsApp.handleEditBug(event)"> 3452 3452 <div class="form-group"> 3453 3453 <label for="bug-title">Title *</label> 3454 3454 <input type="text" id="bug-title" required maxlength="100" value="${esc(bug.title)}"> ··· 3457 3457 <div class="form-group"> 3458 3458 <label for="bug-namespace">Namespace *</label> 3459 3459 <p class="hint">e.g., social.grain, app.bsky, fm.teal</p> 3460 - <input type="text" id="bug-namespace" required value="${esc(bug.namespace)}" onblur="validateNamespaceOnBlur()"> 3460 + <input type="text" id="bug-namespace" required value="${esc(bug.namespace)}" onblur="BugsApp.validateNamespaceOnBlur()"> 3461 3461 <div class="error" id="namespace-error"></div> 3462 3462 </div> 3463 3463 ··· 3492 3492 <label>Attachments</label> 3493 3493 <div id="edit-attachments-list"></div> 3494 3494 <div class="image-upload" onclick="document.getElementById('edit-images').click()"> 3495 - <input type="file" id="edit-images" accept="image/*" multiple onchange="handleEditImageSelect(event)"> 3495 + <input type="file" id="edit-images" accept="image/*" multiple onchange="BugsApp.handleEditImageSelect(event)"> 3496 3496 <p>Click to add images</p> 3497 3497 </div> 3498 3498 <div id="edit-new-images"></div> 3499 3499 </div> 3500 3500 3501 3501 <div class="form-actions"> 3502 - <button type="button" class="btn btn-secondary" onclick="closeModal()">Cancel</button> 3502 + <button type="button" class="btn btn-secondary" onclick="BugsApp.closeModal()">Cancel</button> 3503 3503 <button type="submit" class="btn btn-primary" id="submit-bug-btn">Save Changes</button> 3504 3504 </div> 3505 3505 </form> ··· 3526 3526 (img, index) => ` 3527 3527 <div class="image-preview"> 3528 3528 <img src="${esc(img.image.url)}" alt="${esc(img.alt || "Attachment")}"> 3529 - <button type="button" class="remove-image" onclick="removeExistingAttachment(${index})">×</button> 3529 + <button type="button" class="remove-image" onclick="BugsApp.removeExistingAttachment(${index})">×</button> 3530 3530 </div> 3531 3531 `, 3532 3532 ) ··· 3586 3586 (img, index) => ` 3587 3587 <div class="image-preview"> 3588 3588 <img src="${img.dataUrl}" alt="New attachment"> 3589 - <button type="button" class="remove-image" onclick="removeEditPendingImage(${index})">×</button> 3589 + <button type="button" class="remove-image" onclick="BugsApp.removeEditPendingImage(${index})">×</button> 3590 3590 </div> 3591 3591 `, 3592 3592 ) ··· 3631 3631 document.getElementById("namespace-error").innerHTML = ` 3632 3632 This looks like a domain. Did you mean <strong>${esc(suggested)}</strong>? 3633 3633 <div style="margin-top: 0.5rem;"> 3634 - <button type="button" class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="useSuggestedNamespace('${esc(suggested)}')">Use ${esc(suggested)}</button> 3635 - <button type="button" class="btn btn-secondary" style="margin-left: 0.25rem; padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="confirmNamespace()">Keep as-is</button> 3634 + <button type="button" class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="BugsApp.useSuggestedNamespace('${esc(suggested)}')">Use ${esc(suggested)}</button> 3635 + <button type="button" class="btn btn-secondary" style="margin-left: 0.25rem; padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="BugsApp.confirmNamespace()">Keep as-is</button> 3636 3636 </div> 3637 3637 `; 3638 3638 document.getElementById("bug-namespace").parentElement.classList.add("has-error"); ··· 3850 3850 (img, i) => ` 3851 3851 <div class="image-preview"> 3852 3852 <img src="${img.dataUrl}" alt="Preview"> 3853 - <button type="button" onclick="removeImage(${i})">×</button> 3853 + <button type="button" onclick="BugsApp.removeImage(${i})">×</button> 3854 3854 </div> 3855 3855 `, 3856 3856 ) ··· 3890 3890 document.getElementById("namespace-error").innerHTML = ` 3891 3891 This looks like a domain. Did you mean <strong>${esc(suggested)}</strong>? 3892 3892 <div style="margin-top: 0.5rem;"> 3893 - <button type="button" class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="useSuggestedNamespace('${esc(suggested)}')">Use ${esc(suggested)}</button> 3894 - <button type="button" class="btn btn-secondary" style="margin-left: 0.25rem; padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="confirmNamespace()">Keep as-is</button> 3893 + <button type="button" class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="BugsApp.useSuggestedNamespace('${esc(suggested)}')">Use ${esc(suggested)}</button> 3894 + <button type="button" class="btn btn-secondary" style="margin-left: 0.25rem; padding: 0.25rem 0.5rem; font-size: 0.875rem;" onclick="BugsApp.confirmNamespace()">Keep as-is</button> 3895 3895 </div> 3896 3896 `; 3897 3897 document.getElementById("bug-namespace").parentElement.classList.add("has-error"); ··· 3979 3979 el.style.background = "#dcfce7"; 3980 3980 el.style.borderColor = "#86efac"; 3981 3981 el.style.color = "#16a34a"; 3982 - el.innerHTML = `<span>✓ ${esc(msg)}</span><button style="color: #16a34a" onclick="hideError()">×</button>`; 3982 + el.innerHTML = `<span>✓ ${esc(msg)}</span><button style="color: #16a34a" onclick="BugsApp.hideError()">×</button>`; 3983 3983 el.classList.remove("hidden"); 3984 3984 setTimeout(hideError, 3000); 3985 3985 } ··· 4073 4073 function renderHeaderComponent() { 4074 4074 const header = document.getElementById("header"); 4075 4075 4076 - let left = `<h1>🐛 Bug Tracker</h1> <button class="btn-info" onclick="openInfoModal()" title="How it works">?</button>`; 4076 + let left = `<h1>🐛 Bug Tracker</h1> <button class="btn-info" onclick="BugsApp.openInfoModal()" title="How it works">?</button>`; 4077 4077 if (state.namespace) { 4078 4078 left = ` 4079 4079 <div class="breadcrumb"> 4080 - <a href="?" onclick="navigateHome(event)">🐛 Bug Tracker</a> 4081 - <button class="btn-info" onclick="openInfoModal()" title="How it works">?</button> 4080 + <a href="?" onclick="BugsApp.navigateHome(event)">🐛 Bug Tracker</a> 4081 + <button class="btn-info" onclick="BugsApp.openInfoModal()" title="How it works">?</button> 4082 4082 <span>/</span> 4083 4083 <span>${esc(state.namespace)}</span> 4084 4084 </div> ··· 4088 4088 const right = state.viewer 4089 4089 ? `<div class="user-status"> 4090 4090 ${renderAvatar(state.viewer.appBskyActorProfileByDid, state.viewer.handle, "user-avatar-xl user-avatar-ring")} 4091 - <button class="btn-icon" onclick="logout()" title="Logout"><i data-lucide="log-out"></i></button> 4091 + <button class="btn-icon" onclick="BugsApp.logout()" title="Logout"><i data-lucide="log-out"></i></button> 4092 4092 </div>` 4093 - : `<button class="btn btn-primary" onclick="login()">Login</button>`; 4093 + : `<button class="btn btn-primary" onclick="BugsApp.login()">Login</button>`; 4094 4094 4095 4095 header.innerHTML = `${left}<div class="user-status">${right}</div>`; 4096 4096 } ··· 4221 4221 .map( 4222 4222 (actor, i) => ` 4223 4223 <li class="autocomplete-item ${i === state.handleSuggestionIndex ? "active" : ""}" 4224 - onmousedown="selectHandleSuggestion(${i})"> 4224 + onmousedown="BugsApp.selectHandleSuggestion(${i})"> 4225 4225 <div class="autocomplete-avatar"> 4226 4226 ${actor.avatar ? `<img src="${esc(actor.avatar)}" alt="">` : ""} 4227 4227 </div> ··· 4261 4261 // Show login modal to get handle 4262 4262 const modal = document.getElementById("modal"); 4263 4263 modal.innerHTML = ` 4264 - <div class="modal-backdrop" onclick="closeModal()"></div> 4264 + <div class="modal-backdrop" onclick="BugsApp.closeModal()"></div> 4265 4265 <div class="modal-content"> 4266 4266 <div class="modal-header"> 4267 4267 <h2>Login</h2> 4268 - <button class="modal-close" onclick="closeModal()">×</button> 4268 + <button class="modal-close" onclick="BugsApp.closeModal()">×</button> 4269 4269 </div> 4270 4270 <div class="modal-body"> 4271 - <form onsubmit="handleLogin(event)"> 4271 + <form onsubmit="BugsApp.handleLogin(event)"> 4272 4272 <div class="form-group handle-autocomplete"> 4273 4273 <label for="login-handle">Sign in with your <a href="https://internethandle.org/" target="_blank">internet handle</a></label> 4274 4274 <input type="text" id="login-handle" required placeholder="you.bsky.social" autocomplete="off" data-1p-ignore 4275 - oninput="handleHandleInput(event)" 4276 - onkeydown="handleHandleKeydown(event)" 4277 - onfocusout="setTimeout(() => clearHandleSuggestions(), 150)"> 4275 + oninput="BugsApp.handleHandleInput(event)" 4276 + onkeydown="BugsApp.handleHandleKeydown(event)" 4277 + onfocusout="setTimeout(() => BugsApp.clearHandleSuggestions(), 150)"> 4278 4278 <ul id="handle-suggestions" class="autocomplete-menu"></ul> 4279 4279 </div> 4280 4280 <div class="form-actions"> 4281 - <button type="button" class="btn btn-secondary" onclick="closeModal()">Cancel</button> 4281 + <button type="button" class="btn btn-secondary" onclick="BugsApp.closeModal()">Cancel</button> 4282 4282 <button type="submit" class="btn btn-primary">Login</button> 4283 4283 </div> 4284 4284 </form> ··· 4335 4335 state.viewer = null; 4336 4336 } 4337 4337 } 4338 + 4339 + // Expose functions via namespace for inline event handlers 4340 + // (required because script type="module" scopes everything to the module) 4341 + window.BugsApp = { 4342 + // Error display 4343 + hideError, 4344 + // Namespace suggestions 4345 + useSuggestedNamespace, 4346 + dismissNamespaceSuggestion, 4347 + confirmNamespace, 4348 + validateNamespaceOnBlur, 4349 + // Navigation 4350 + navigateToNamespace, 4351 + navigateHome, 4352 + // Bug list 4353 + handleReportBug, 4354 + handleSeverityFilter, 4355 + loadMoreBugs, 4356 + openBugDetail, 4357 + // Overlay/modal 4358 + closeOverlay, 4359 + closeModal, 4360 + openInfoModal, 4361 + // Bug actions 4362 + shareBug, 4363 + openEditModal, 4364 + handleDeleteBug, 4365 + handleSubmitBug, 4366 + handleEditBug, 4367 + // Images 4368 + handleImageSelect, 4369 + handleEditImageSelect, 4370 + removeImage, 4371 + removeExistingAttachment, 4372 + removeEditPendingImage, 4373 + openLightbox, 4374 + // Link issue modal 4375 + openLinkIssueModal, 4376 + closeLinkIssueModal, 4377 + selectRepoForLinking, 4378 + goBackToRepos, 4379 + createAndLinkIssue, 4380 + linkToExistingIssue, 4381 + unlinkIssue, 4382 + // Comments 4383 + handleSubmitComment, 4384 + handleDeleteComment, 4385 + startEditComment, 4386 + cancelEditComment, 4387 + handleSaveEditComment, 4388 + removeEditCommentAttachment, 4389 + showReplyForm, 4390 + cancelReply, 4391 + handleCommentImageSelect, 4392 + removeCommentImage, 4393 + // Responses 4394 + handleSubmitResponse, 4395 + handleDeleteResponse, 4396 + // Auth 4397 + login, 4398 + logout, 4399 + handleLogin, 4400 + handleHandleInput, 4401 + handleHandleKeydown, 4402 + selectHandleSuggestion, 4403 + clearHandleSuggestions, 4404 + }; 4338 4405 4339 4406 main(); 4340 4407 </script>