444444 font-weight : 600 ;
445445 }
446446
447+ /* JSON format toggle */
448+ .format-toggle-row {
449+ display : flex;
450+ align-items : center;
451+ gap : 8px ;
452+ margin-bottom : 8px ;
453+ }
454+
455+ .format-toggle-row label {
456+ display : flex;
457+ align-items : center;
458+ gap : 6px ;
459+ font-weight : 400 ;
460+ font-size : 0.85rem ;
461+ cursor : pointer;
462+ }
463+
464+ .toggle-switch {
465+ position : relative;
466+ width : 36px ;
467+ height : 20px ;
468+ background : # d1d5db ;
469+ border-radius : 999px ;
470+ cursor : pointer;
471+ transition : background 0.2s ;
472+ }
473+
474+ .toggle-switch .active {
475+ background : # 2563eb ;
476+ }
477+
478+ .toggle-switch ::after {
479+ content : "" ;
480+ position : absolute;
481+ top : 2px ;
482+ left : 2px ;
483+ width : 16px ;
484+ height : 16px ;
485+ background : # fff ;
486+ border-radius : 50% ;
487+ transition : transform 0.2s ;
488+ }
489+
490+ .toggle-switch .active ::after {
491+ transform : translateX (16px );
492+ }
493+
447494 @media (max-width : 640px ) {
448495 .card {
449496 padding : 14px 14px 16px ;
@@ -617,6 +664,12 @@ <h1>CORS Fetch Tester</h1>
617664 Body
618665 < small > Shown as text (UTF-8)</ small >
619666 </ div >
667+ < div id ="format-toggle-container " class ="format-toggle-row hidden ">
668+ < label >
669+ < span id ="format-toggle " class ="toggle-switch active "> </ span >
670+ < span > Format JSON</ span >
671+ </ label >
672+ </ div >
620673 < pre id ="body-output "> // Response body will appear here</ pre >
621674 </ div >
622675 </ div >
@@ -655,16 +708,67 @@ <h1>CORS Fetch Tester</h1>
655708 const headersContainer = document . getElementById ( "headers-container" ) ;
656709 const bodyOutput = document . getElementById ( "body-output" ) ;
657710
711+ // JSON format toggle
712+ const formatToggleContainer = document . getElementById ( "format-toggle-container" ) ;
713+ const formatToggle = document . getElementById ( "format-toggle" ) ;
714+
658715 // State
659716 let kvPairs = [ ] ;
660717 let headerPairs = [ ] ;
718+ let responseBodyRaw = "" ;
719+ let responseBodyFormatted = "" ;
720+ let responseIsJson = false ;
721+ let showFormatted = true ;
661722
662723 // Headers collapsible
663724 headersToggle . addEventListener ( "click" , ( ) => {
664725 headersToggle . classList . toggle ( "collapsed" ) ;
665726 headersEditorContainer . classList . toggle ( "collapsed" ) ;
666727 } ) ;
667728
729+ // JSON format toggle
730+ formatToggle . addEventListener ( "click" , ( ) => {
731+ showFormatted = ! showFormatted ;
732+ formatToggle . classList . toggle ( "active" , showFormatted ) ;
733+ updateBodyDisplay ( ) ;
734+ } ) ;
735+
736+ function updateBodyDisplay ( ) {
737+ if ( responseIsJson ) {
738+ bodyOutput . textContent = showFormatted ? responseBodyFormatted : responseBodyRaw ;
739+ } else {
740+ bodyOutput . textContent = responseBodyRaw ;
741+ }
742+ }
743+
744+ function setResponseBody ( text ) {
745+ responseBodyRaw = text ;
746+ responseIsJson = false ;
747+ responseBodyFormatted = "" ;
748+
749+ // Try to parse as JSON
750+ if ( text ) {
751+ try {
752+ const parsed = JSON . parse ( text ) ;
753+ responseBodyFormatted = JSON . stringify ( parsed , null , 2 ) ;
754+ responseIsJson = true ;
755+ } catch {
756+ // Not valid JSON
757+ }
758+ }
759+
760+ // Show/hide toggle based on whether response is JSON
761+ formatToggleContainer . classList . toggle ( "hidden" , ! responseIsJson ) ;
762+
763+ // Reset to formatted view when new JSON response comes in
764+ if ( responseIsJson ) {
765+ showFormatted = true ;
766+ formatToggle . classList . add ( "active" ) ;
767+ }
768+
769+ updateBodyDisplay ( ) ;
770+ }
771+
668772 function updateHeadersCount ( ) {
669773 const count = headerPairs . filter ( h => h . key . trim ( ) ) . length ;
670774 headersCountEl . textContent = count ;
@@ -1214,6 +1318,7 @@ <h1>CORS Fetch Tester</h1>
12141318 setStatus ( "Pending request…" , null ) ;
12151319 effectiveUrlEl . textContent = "" ;
12161320 headersContainer . innerHTML = '<div class="hint">Awaiting response…</div>' ;
1321+ formatToggleContainer . classList . add ( "hidden" ) ;
12171322 bodyOutput . textContent = "" ;
12181323
12191324 updateFragmentFromState ( ) ;
@@ -1249,6 +1354,7 @@ <h1>CORS Fetch Tester</h1>
12491354 renderResponseHeaders ( response . headers ) ;
12501355
12511356 if ( method === "HEAD" ) {
1357+ formatToggleContainer . classList . add ( "hidden" ) ;
12521358 bodyOutput . textContent = "// HEAD requests do not return a body." ;
12531359 } else {
12541360 let bodyText ;
@@ -1259,9 +1365,10 @@ <h1>CORS Fetch Tester</h1>
12591365 }
12601366
12611367 if ( ! bodyText ) {
1368+ formatToggleContainer . classList . add ( "hidden" ) ;
12621369 bodyOutput . textContent = "// Empty response body (or body already consumed)." ;
12631370 } else {
1264- bodyOutput . textContent = bodyText ;
1371+ setResponseBody ( bodyText ) ;
12651372 }
12661373 }
12671374 } catch ( err ) {
@@ -1273,6 +1380,7 @@ <h1>CORS Fetch Tester</h1>
12731380 String ( err ) ;
12741381 headersContainer . innerHTML =
12751382 '<div class="hint">No headers: the browser blocked access to the response.</div>' ;
1383+ formatToggleContainer . classList . add ( "hidden" ) ;
12761384 bodyOutput . textContent =
12771385 "// No body: the browser blocked access to the response.\n" +
12781386 "// This usually means the server has not enabled CORS for this origin." ;
0 commit comments