Skip to content

Commit 4714aab

Browse files
committed
feat(formatter): auto-format pasted JSON
1 parent 5376888 commit 4714aab

1 file changed

Lines changed: 53 additions & 6 deletions

File tree

src/components/json-formatter/index.tsx

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { useState, useCallback, useMemo } from 'react';
2-
import Editor, { type BeforeMount } from '@monaco-editor/react';
1+
import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
2+
import Editor, { type BeforeMount, type OnMount } from '@monaco-editor/react';
3+
import type { IDisposable } from 'monaco-editor';
34
import {
45
DEFAULT_JSON,
56
VITESSE_DARK_MONACO_THEME,
@@ -15,10 +16,19 @@ interface JsonFormatterProps {
1516
onThemeChange: (isDark: boolean) => void;
1617
}
1718

19+
function formatJsonValue(value: string) {
20+
return JSON.stringify(JSON.parse(value), null, 2);
21+
}
22+
23+
function minifyJsonValue(value: string) {
24+
return JSON.stringify(JSON.parse(value));
25+
}
26+
1827
export default function JsonFormatter({ isDarkMode, onThemeChange }: JsonFormatterProps) {
1928
const [jsonText, setJsonText] = useState(DEFAULT_JSON);
2029
const [error, setError] = useState('');
2130
const [success, setSuccess] = useState('');
31+
const pasteDisposableRef = useRef<IDisposable | null>(null);
2232
const jsonValidation = useMemo(() => validateJson(jsonText), [jsonText]);
2333

2434
const applyJsonValue = useCallback((value: string) => {
@@ -29,8 +39,7 @@ export default function JsonFormatter({ isDarkMode, onThemeChange }: JsonFormatt
2939

3040
const formatJson = useCallback(() => {
3141
try {
32-
const parsed = JSON.parse(jsonText);
33-
const formatted = JSON.stringify(parsed, null, 2);
42+
const formatted = formatJsonValue(jsonText);
3443
setJsonText(formatted);
3544
setError('');
3645
setSuccess('JSON formatted successfully!');
@@ -50,8 +59,7 @@ export default function JsonFormatter({ isDarkMode, onThemeChange }: JsonFormatt
5059

5160
const minifyJson = useCallback(() => {
5261
try {
53-
const parsed = JSON.parse(jsonText);
54-
const minified = JSON.stringify(parsed);
62+
const minified = minifyJsonValue(jsonText);
5563
setJsonText(minified);
5664
setError('');
5765
setSuccess('JSON minified successfully!');
@@ -113,6 +121,44 @@ export default function JsonFormatter({ isDarkMode, onThemeChange }: JsonFormatt
113121
monaco.editor.defineTheme(VITESSE_LIGHT_THEME, VITESSE_LIGHT_MONACO_THEME);
114122
}, []);
115123

124+
const handleEditorMount = useCallback<OnMount>((editor) => {
125+
pasteDisposableRef.current?.dispose();
126+
pasteDisposableRef.current = editor.onDidPaste(() => {
127+
const value = editor.getValue();
128+
129+
try {
130+
const formatted = formatJsonValue(value);
131+
132+
if (formatted !== value) {
133+
const model = editor.getModel();
134+
135+
if (model) {
136+
editor.executeEdits('auto-format-paste', [
137+
{
138+
range: model.getFullModelRange(),
139+
text: formatted,
140+
forceMoveMarkers: true,
141+
},
142+
]);
143+
editor.pushUndoStop();
144+
}
145+
}
146+
147+
setError('');
148+
setSuccess('JSON formatted successfully!');
149+
setTimeout(() => setSuccess(''), 3000);
150+
} catch {
151+
// Keep the pasted text unchanged; the existing validation status shows the parse error.
152+
}
153+
});
154+
}, []);
155+
156+
useEffect(() => {
157+
return () => {
158+
pasteDisposableRef.current?.dispose();
159+
};
160+
}, []);
161+
116162
const themeClass = isDarkMode ? 'dark' : 'light';
117163
const editorTheme = isDarkMode ? VITESSE_DARK_THEME : VITESSE_LIGHT_THEME;
118164
const validationError =
@@ -177,6 +223,7 @@ export default function JsonFormatter({ isDarkMode, onThemeChange }: JsonFormatt
177223
value={jsonText}
178224
onChange={handleInputChange}
179225
beforeMount={registerEditorThemes}
226+
onMount={handleEditorMount}
180227
theme={editorTheme}
181228
options={{
182229
contextmenu: false,

0 commit comments

Comments
 (0)