diff --git a/src/features/editor/TextEditor.tsx b/src/features/editor/TextEditor.tsx index f09ac78fb2e..e49ca5b795a 100644 --- a/src/features/editor/TextEditor.tsx +++ b/src/features/editor/TextEditor.tsx @@ -72,6 +72,28 @@ const TextEditor = () => { }); }, []); + const handleValidate = useCallback( + (errors: any[]) => { + // Let Monaco handle syntax highlighting errors, but don't interfere with our validation + // Monaco errors are mainly for syntax highlighting and basic JSON structure + const monacoError = errors[0]?.message; + if (monacoError && !useFile.getState().error) { + // Only set Monaco errors if we don't have custom validation errors + setError(monacoError); + } + }, + [setError] + ); + + const handleChange = useCallback( + (newContents: string | undefined) => { + if (newContents !== undefined) { + setContents({ contents: newContents, skipUpdate: true }); + } + }, + [setContents] + ); + return ( @@ -82,8 +104,8 @@ const TextEditor = () => { value={contents} options={editorOptions} onMount={handleMount} - onValidate={errors => setError(errors[0]?.message)} - onChange={contents => setContents({ contents, skipUpdate: true })} + onValidate={handleValidate} + onChange={handleChange} loading={} /> diff --git a/src/lib/utils/jsonAdapter.ts b/src/lib/utils/jsonAdapter.ts index f0df0b908b5..219f9256d06 100644 --- a/src/lib/utils/jsonAdapter.ts +++ b/src/lib/utils/jsonAdapter.ts @@ -10,7 +10,18 @@ export const contentToJson = (value: string, format = FileFormat.JSON): Promise< const { parse } = await import("jsonc-parser"); const errors: ParseError[] = []; const result = parse(value, errors); - if (errors.length > 0) JSON.parse(value); + + // If there are parsing errors from jsonc-parser, fall back to strict JSON.parse + if (errors.length > 0) { + try { + const strictResult = JSON.parse(value); + return resolve(strictResult); + } catch (jsonError) { + // If both parsers fail, reject with the JSON error which is more standard + return reject(jsonError); + } + } + return resolve(result); } diff --git a/src/store/useFile.ts b/src/store/useFile.ts index 193118198f6..ecf899a102f 100644 --- a/src/store/useFile.ts +++ b/src/store/useFile.ts @@ -98,15 +98,17 @@ const isURL = (value: string) => { ); }; -const debouncedUpdateJson = debounce((value: unknown) => { - useGraph.getState().setLoading(true); - useJson.getState().setJson(JSON.stringify(value, null, 2)); +const debouncedUpdateJson = debounce((value: unknown, hasError: boolean) => { + if (!hasError) { + useGraph.getState().setLoading(true); + useJson.getState().setJson(JSON.stringify(value, null, 2)); + } }, 800); const useFile = create()((set, get) => ({ ...initialStates, clear: () => { - set({ contents: "" }); + set({ contents: "", error: null, hasChanges: false }); useJson.getState().clear(); }, setJsonSchema: jsonSchema => set({ jsonSchema }), @@ -136,12 +138,14 @@ const useFile = create()((set, get) => ({ try { set({ ...(contents && { contents }), - error: null, + error: null, // Clear any previous errors when new content is set hasChanges, format: format ?? get().format, }); const isFetchURL = window.location.href.includes("?"); + + // Try to parse the JSON to validate it const json = await contentToJson(get().contents, get().format); if (!useConfig.getState().liveTransformEnabled && skipUpdate) return; @@ -152,10 +156,24 @@ const useFile = create()((set, get) => ({ set({ hasChanges: true }); } - debouncedUpdateJson(json); + // Only update JSON if there's no error + debouncedUpdateJson(json, false); } catch (error: any) { - if (error?.mark?.snippet) return set({ error: error.mark.snippet }); - if (error?.message) set({ error: error.message }); + // Handle validation errors properly + let errorMessage = "Invalid format"; + + if (error?.mark?.snippet) { + errorMessage = error.mark.snippet; + } else if (error?.message) { + errorMessage = error.message; + } else if (typeof error === "string") { + errorMessage = error; + } + + set({ error: errorMessage }); + + // Don't update JSON when there's an error, and stop loading states + debouncedUpdateJson({}, true); useJson.setState({ loading: false }); useGraph.setState({ loading: false }); }