Better content-type detection for editor

This commit is contained in:
Gregory Schier
2024-08-29 06:07:31 -07:00
parent 690ef02a38
commit fdc60445c8
13 changed files with 53 additions and 32 deletions

View File

@@ -128,7 +128,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
return (
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto]">
<Editor
contentType="application/graphql"
language="graphql"
defaultValue={query ?? ''}
format={formatGraphQL}
heightMode="auto"
@@ -144,7 +144,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
</Separator>
<Editor
format={tryFormatJson}
contentType="application/json"
language="json"
defaultValue={JSON.stringify(variables, null, 2)}
heightMode="auto"
onChange={handleChangeVariables}

View File

@@ -185,7 +185,7 @@ export function GrpcEditor({
return (
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
<Editor
contentType="application/json"
language="json"
autocompleteVariables
useTemplating
forceUpdateKey={request.id}

View File

@@ -12,6 +12,7 @@ import { useRequests } from '../hooks/useRequests';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { languageFromContentType } from '../lib/contentType';
import { tryFormatJson } from '../lib/formatters';
import {
AUTH_TYPE_BASIC,
@@ -355,7 +356,7 @@ export const RequestPane = memo(function RequestPane({
placeholder="..."
heightMode={fullHeight ? 'full' : 'auto'}
defaultValue={`${activeRequest.body?.text ?? ''}`}
contentType="application/json"
language="json"
onChange={handleBodyTextChange}
format={tryFormatJson}
/>
@@ -367,7 +368,7 @@ export const RequestPane = memo(function RequestPane({
placeholder="..."
heightMode={fullHeight ? 'full' : 'auto'}
defaultValue={`${activeRequest.body?.text ?? ''}`}
contentType="text/xml"
language="xml"
onChange={handleBodyTextChange}
/>
) : activeRequest.bodyType === BODY_TYPE_GRAPHQL ? (
@@ -402,6 +403,7 @@ export const RequestPane = memo(function RequestPane({
forceUpdateKey={forceUpdateKey}
useTemplating
autocompleteVariables
language={languageFromContentType(contentType)}
placeholder="..."
heightMode={fullHeight ? 'full' : 'auto'}
defaultValue={`${activeRequest.body?.text ?? ''}`}

View File

@@ -198,7 +198,7 @@ export function SettingsAppearance() {
'};',
].join('\n')}
heightMode="auto"
contentType="application/javascript"
language="javascript"
/>
</VStack>
</VStack>

View File

@@ -117,7 +117,7 @@ export function SettingsDesign() {
'};',
].join('\n')}
heightMode="auto"
contentType="application/javascript"
language="javascript"
/>
<div className="flex flex-col gap-1">
<div className="flex flex-wrap gap-1">

View File

@@ -67,7 +67,7 @@ export const UrlBar = memo(function UrlBar({
wrapLines={isFocused}
hideLabel
useTemplating
contentType="url"
language="url"
className="pl-0 pr-1.5 py-0.5"
name="url"
label="Enter URL"

View File

@@ -36,7 +36,7 @@ export function BulkPairEditor({
forceUpdateKey={forceUpdateKey}
placeholder={`${namePlaceholder ?? 'name'}: ${valuePlaceholder ?? 'value'}`}
defaultValue={pairsText}
contentType="pairs"
language="pairs"
onChange={handleChange}
/>
);

View File

@@ -42,7 +42,7 @@ export interface EditorProps {
type?: 'text' | 'password';
className?: string;
heightMode?: 'auto' | 'full';
contentType?: string | null;
language?: 'javascript' | 'json' | 'html' | 'xml' | 'graphql' | 'url' | 'pairs' | 'text';
forceUpdateKey?: string | number;
autoFocus?: boolean;
autoSelect?: boolean;
@@ -71,7 +71,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
readOnly,
type = 'text',
heightMode,
contentType,
language = 'text',
autoFocus,
autoSelect,
placeholder,
@@ -226,12 +226,12 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
[dialog],
);
// Update language extension when contentType changes
// Update the language extension when the language changes
useEffect(() => {
if (cm.current === null) return;
const { view, languageCompartment } = cm.current;
const ext = getLanguageExtension({
contentType,
language,
environmentVariables,
useTemplating,
autocomplete,
@@ -242,7 +242,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
});
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
}, [
contentType,
language,
autocomplete,
useTemplating,
environmentVariables,
@@ -265,7 +265,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
try {
const languageCompartment = new Compartment();
const langExt = getLanguageExtension({
contentType,
language,
useTemplating,
autocomplete,
environmentVariables,

View File

@@ -32,7 +32,7 @@ import {
} from '@codemirror/view';
import { tags as t } from '@lezer/highlight';
import type { EnvironmentVariable, TemplateFunction } from '@yaakapp/api';
import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
import { graphql } from 'cm6-graphql';
import { EditorView } from 'codemirror';
import type { EditorProps } from './index';
import { pairs } from './pairs/extension';
@@ -64,19 +64,19 @@ export const syntaxHighlightStyle = HighlightStyle.define([
const syntaxTheme = EditorView.theme({}, { dark: true });
const syntaxExtensions: Record<string, LanguageSupport> = {
'application/graphql': graphqlLanguageSupport(),
'application/json': json(),
'application/javascript': javascript(),
'text/html': xml(), // HTML as XML because HTML is oddly slow
'application/xml': xml(),
'text/xml': xml(),
const syntaxExtensions: Record<NonNullable<EditorProps['language']>, LanguageSupport | null> = {
graphql: null,
json: json(),
javascript: javascript(),
html: xml(), // HTML as XML because HTML is oddly slow
xml: xml(),
url: url(),
pairs: pairs(),
text: text(),
};
export function getLanguageExtension({
contentType,
language,
useTemplating = false,
environmentVariables,
autocomplete,
@@ -90,12 +90,12 @@ export function getLanguageExtension({
onClickFunction: (option: TemplateFunction, tagValue: string, startPos: number) => void;
onClickVariable: (option: EnvironmentVariable, tagValue: string, startPos: number) => void;
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void;
} & Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete'>) {
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
if (justContentType === 'application/graphql') {
} & Pick<EditorProps, 'language' | 'useTemplating' | 'autocomplete'>) {
if (language === 'graphql') {
return graphql();
}
const base = syntaxExtensions[justContentType] ?? text();
const base = syntaxExtensions[language ?? 'text'] ?? text();
if (!useTemplating) {
return base;
}

View File

@@ -14,7 +14,7 @@ export type InputProps = Omit<
> &
Pick<
EditorProps,
| 'contentType'
| 'language'
| 'useTemplating'
| 'autocomplete'
| 'forceUpdateKey'

View File

@@ -1,3 +1,4 @@
import type { HttpResponse } from '@yaakapp/api';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import { useCallback, useMemo } from 'react';
@@ -8,8 +9,8 @@ import { useFilterResponse } from '../../hooks/useFilterResponse';
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
import { useSaveResponse } from '../../hooks/useSaveResponse';
import { useToggle } from '../../hooks/useToggle';
import { languageFromContentType } from '../../lib/contentType';
import { tryFormatJson, tryFormatXml } from '../../lib/formatters';
import type { HttpResponse } from '@yaakapp/api';
import { CopyButton } from '../CopyButton';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
@@ -63,6 +64,8 @@ export function TextViewer({ response, pretty, className }: Props) {
}
}, [isSearching, setFilterText]);
console.log('HELLO', contentType);
const isJson = contentType?.includes('json');
const isXml = contentType?.includes('xml') || contentType?.includes('html');
const canFilter = isJson || isXml;
@@ -176,7 +179,7 @@ export function TextViewer({ response, pretty, className }: Props) {
className={className}
forceUpdateKey={body}
defaultValue={body}
contentType={contentType}
language={languageFromContentType(contentType)}
actions={actions}
extraExtensions={extraExtensions}
/>

View File

@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import type { HttpResponseHeader } from '@yaakapp/api';
import { useMemo } from 'react';
export function useContentTypeFromHeaders(headers: HttpResponseHeader[] | null): string | null {
return useMemo(

View File

@@ -0,0 +1,16 @@
import type { EditorProps } from '../components/core/Editor';
export function languageFromContentType(contentType: string | null): EditorProps['language'] {
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
if (justContentType.includes('json')) {
return 'json';
} else if (justContentType.includes('xml')) {
return 'xml';
} else if (justContentType.includes('html')) {
return 'html';
} else if (justContentType.includes('javascript')) {
return 'javascript';
} else {
return 'text';
}
}