@@ -12,6 +12,14 @@ type BodyFormat =
1212 | 'form-urlencoded'
1313 | 'form-data' ;
1414
15+ // Font-size bounds for the request-body editor (px). The default matches the
16+ // Monaco editors and the fallback textarea. The line-height ratio keeps the
17+ // syntax-highlight overlay aligned with the textarea as the size changes.
18+ const MIN_BODY_FONT_SIZE = 8 ;
19+ const MAX_BODY_FONT_SIZE = 28 ;
20+ const DEFAULT_BODY_FONT_SIZE = 12 ;
21+ const BODY_LINE_HEIGHT_RATIO = 16 / 12 ;
22+
1523interface FormDataField {
1624 key : string ;
1725 value : string ;
@@ -49,6 +57,8 @@ export class RequestBodyEditor {
4957 private monacoXmlEditor : MonacoXmlEditor | null = null ;
5058 private forcedContentType ?: string ;
5159 private formDataFields : FormDataField [ ] = [ ] ;
60+ /** Current request-body editor font size in px (shared by all editors). */
61+ private currentFontSize = DEFAULT_BODY_FONT_SIZE ;
5262 /**
5363 * Depth counter for programmatic load operations (setBody / clear / clearEditors).
5464 * While > 0, all onBodyChange / onContentTypeChange callbacks are suppressed so
@@ -134,6 +144,30 @@ export class RequestBodyEditor {
134144 <div class="title">Payload</div>
135145 </div>
136146 <div class="body-type-selector">
147+ <span
148+ id="body-font-controls"
149+ class="body-font-controls"
150+ style="display: none;"
151+ >
152+ <button
153+ id="body-font-dec"
154+ class="body-font-btn"
155+ type="button"
156+ title="Decrease font size"
157+ aria-label="Decrease font size"
158+ >
159+ A−
160+ </button>
161+ <button
162+ id="body-font-inc"
163+ class="body-font-btn"
164+ type="button"
165+ title="Increase font size"
166+ aria-label="Increase font size"
167+ >
168+ A+
169+ </button>
170+ </span>
137171 <button
138172 id="body-format-btn"
139173 class="body-format-btn"
@@ -228,12 +262,20 @@ export class RequestBodyEditor {
228262 // Watch for body section becoming visible to reset scroll position
229263 this . setupVisibilityWatcher ( ) ;
230264
231- // Format button
265+ // Format button (JSON + XML)
232266 const formatBtn = this . container . querySelector ( '#body-format-btn' ) ;
233267 if ( formatBtn ) {
234- formatBtn . addEventListener ( 'click' , ( ) => this . formatJson ( ) ) ;
268+ formatBtn . addEventListener ( 'click' , ( ) => this . formatBody ( ) ) ;
235269 }
236270
271+ // Font size controls (A- / A+)
272+ this . container
273+ . querySelector ( '#body-font-dec' )
274+ ?. addEventListener ( 'click' , ( ) => this . adjustFontSize ( - 1 ) ) ;
275+ this . container
276+ . querySelector ( '#body-font-inc' )
277+ ?. addEventListener ( 'click' , ( ) => this . adjustFontSize ( 1 ) ) ;
278+
237279 // Form data add button
238280 const addBtn = this . container . querySelector ( '#form-data-add-btn' ) ;
239281 if ( addBtn ) {
@@ -356,6 +398,9 @@ export class RequestBodyEditor {
356398 } ,
357399 } ) ;
358400
401+ // Keep the user's chosen font size across editor switches.
402+ this . applyFontSize ( ) ;
403+
359404 // Focus the Monaco editor
360405 setTimeout ( ( ) => {
361406 this . monacoEditor ?. focus ( ) ;
@@ -403,6 +448,8 @@ export class RequestBodyEditor {
403448 } ,
404449 } ) ;
405450
451+ this . applyFontSize ( ) ;
452+
406453 setTimeout ( ( ) => {
407454 this . monacoXmlEditor ?. focus ( ) ;
408455 } , 100 ) ;
@@ -438,6 +485,7 @@ export class RequestBodyEditor {
438485
439486 // Update highlighting for non-JSON formats
440487 this . updateHighlighting ( ) ;
488+ this . applyFontSize ( ) ;
441489 }
442490
443491 private handleBodyTypeChange ( bodyType : string ) : void {
@@ -465,6 +513,7 @@ export class RequestBodyEditor {
465513 this . currentBodyType = 'none' ;
466514 this . currentFormat = 'json' ;
467515 this . setFormatButtonVisible ( false ) ;
516+ this . setFontControlsVisible ( false ) ;
468517 if ( ! this . isLoadingBody ) {
469518 this . events . onBodyChange ( {
470519 type : this . currentBodyType ,
@@ -481,6 +530,7 @@ export class RequestBodyEditor {
481530 this . currentBodyType = 'form-data' ;
482531 this . currentFormat = 'form-data' ;
483532 this . setFormatButtonVisible ( false ) ;
533+ this . setFontControlsVisible ( false ) ;
484534
485535 // Add a default empty row if no fields exist
486536 if ( this . formDataFields . length === 0 ) {
@@ -511,8 +561,11 @@ export class RequestBodyEditor {
511561 this . currentFormat = selection ;
512562 }
513563
514- // Show/hide JSON-specific actions
515- this . setFormatButtonVisible ( this . currentFormat === 'json' ) ;
564+ // Format button supports JSON + XML; font controls apply to any editor.
565+ this . setFormatButtonVisible (
566+ this . currentFormat === 'json' || this . currentFormat === 'xml'
567+ ) ;
568+ this . setFontControlsVisible ( true ) ;
516569 // Switch editor based on format
517570 if ( this . currentFormat === 'json' ) {
518571 // Use Monaco editor for JSON
@@ -638,8 +691,87 @@ export class RequestBodyEditor {
638691 const formatBtn = this . container . querySelector (
639692 '#body-format-btn'
640693 ) as HTMLElement | null ;
641- if ( formatBtn ) {
642- formatBtn . style . display = visible ? '' : 'none' ;
694+ if ( ! formatBtn ) return ;
695+ formatBtn . style . display = visible ? '' : 'none' ;
696+ if ( visible ) {
697+ const label = this . currentFormat === 'xml' ? 'Format XML' : 'Format JSON' ;
698+ formatBtn . title = label ;
699+ formatBtn . setAttribute ( 'aria-label' , label ) ;
700+ }
701+ }
702+
703+ private setFontControlsVisible ( visible : boolean ) : void {
704+ const controls = this . container . querySelector (
705+ '#body-font-controls'
706+ ) as HTMLElement | null ;
707+ if ( controls ) {
708+ controls . style . display = visible ? '' : 'none' ;
709+ }
710+ }
711+
712+ /** Nudge the editor font size within [MIN, MAX] and re-apply it. */
713+ private adjustFontSize ( delta : number ) : void {
714+ const next = Math . min (
715+ MAX_BODY_FONT_SIZE ,
716+ Math . max ( MIN_BODY_FONT_SIZE , this . currentFontSize + delta )
717+ ) ;
718+ if ( next === this . currentFontSize ) return ;
719+ this . currentFontSize = next ;
720+ this . applyFontSize ( ) ;
721+ }
722+
723+ /**
724+ * Push the current font size to whichever editor is active (Monaco JSON,
725+ * Monaco XML, or the fallback textarea + its highlight overlay). Called after
726+ * every editor switch so the chosen size persists across format changes.
727+ */
728+ private applyFontSize ( ) : void {
729+ this . monacoEditor ?. setFontSize ( this . currentFontSize ) ;
730+ this . monacoXmlEditor ?. setFontSize ( this . currentFontSize ) ;
731+
732+ const textarea = this . container . querySelector (
733+ '#request-body'
734+ ) as HTMLTextAreaElement | null ;
735+ const overlay = this . container . querySelector (
736+ '#syntax-highlight-overlay'
737+ ) as HTMLElement | null ;
738+ const lineHeight = Math . round (
739+ this . currentFontSize * BODY_LINE_HEIGHT_RATIO
740+ ) ;
741+ if ( textarea ) {
742+ textarea . style . fontSize = `${ this . currentFontSize } px` ;
743+ textarea . style . lineHeight = `${ lineHeight } px` ;
744+ }
745+ if ( overlay ) {
746+ overlay . style . fontSize = `${ this . currentFontSize } px` ;
747+ overlay . style . lineHeight = `${ lineHeight } px` ;
748+ }
749+ }
750+
751+ /** Format the current body based on its format (JSON or XML). */
752+ private formatBody ( ) : void {
753+ if ( this . currentFormat === 'xml' ) {
754+ this . formatXml ( ) ;
755+ return ;
756+ }
757+ this . formatJson ( ) ;
758+ }
759+
760+ private formatXml ( ) : void {
761+ if ( this . currentFormat !== 'xml' || ! this . monacoXmlEditor ) {
762+ return ;
763+ }
764+ if ( ! this . monacoXmlEditor . getValue ( ) . trim ( ) ) {
765+ this . events . onStatusUpdate ( 'warning' , 'No XML to format' ) ;
766+ return ;
767+ }
768+ if ( this . monacoXmlEditor . format ( ) ) {
769+ this . events . onStatusUpdate ( 'success' , 'XML formatted successfully' ) ;
770+ } else {
771+ this . events . onStatusUpdate (
772+ 'error' ,
773+ 'Invalid XML: check tags, attributes, and namespaces'
774+ ) ;
643775 }
644776 }
645777
0 commit comments