Skip to content

Commit f31de63

Browse files
committed
Request section fixes
1 parent 2416e76 commit f31de63

10 files changed

Lines changed: 359 additions & 96 deletions

src/renderer/components/request/MonacoXmlEditor.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import * as monaco from 'monaco-editor';
77
import { forceInitialViewportTokenization } from './monaco-tokenization';
8+
import { parseXml, prettyPrintXml } from './soap-xml-helpers';
89

910
export interface MonacoXmlEditorOptions {
1011
container: HTMLElement;
@@ -115,8 +116,22 @@ export class MonacoXmlEditor {
115116
this.editor?.updateOptions({ fontSize: px });
116117
}
117118

118-
public format(): void {
119-
this.editor?.getAction('editor.action.formatDocument')?.run();
119+
/**
120+
* Pretty-print the XML content. Monaco ships no built-in XML formatter, so we
121+
* parse + re-serialize via the shared soap-xml helpers. Returns `true` on
122+
* success, `false` when the body is empty or not well-formed (caller decides
123+
* how to surface the outcome).
124+
*/
125+
public format(): boolean {
126+
if (!this.editor) return false;
127+
const text = this.editor.getValue().trim();
128+
if (!text) return false;
129+
130+
const parsed = parseXml(text);
131+
if (!parsed.valid || !parsed.document) return false;
132+
133+
this.editor.setValue(prettyPrintXml(parsed.document));
134+
return true;
120135
}
121136

122137
public dispose(): void {

src/renderer/components/request/RequestBodyEditor.ts

Lines changed: 138 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
1523
interface 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&minus;
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

src/renderer/components/request/__tests__/RequestBodyEditor.loading.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ vi.mock('../MonacoJsonEditor', () => ({
2727
getValue: vi.fn(() => ''),
2828
focus: vi.fn(),
2929
dispose: vi.fn(),
30+
setFontSize: vi.fn(),
31+
format: vi.fn(),
3032
saveViewState: vi.fn(() => null),
3133
restoreViewState: vi.fn(),
3234
})),
@@ -38,6 +40,8 @@ vi.mock('../MonacoXmlEditor', () => ({
3840
getValue: vi.fn(() => ''),
3941
focus: vi.fn(),
4042
dispose: vi.fn(),
43+
setFontSize: vi.fn(),
44+
format: vi.fn(() => true),
4145
})),
4246
}));
4347

0 commit comments

Comments
 (0)